C++11 should raw pointers ever be used when there is one owner (std::unique_ptr) but other objects need a "handle" to the object?

StackOverflow https://stackoverflow.com/questions/23549744

  •  18-07-2023
  •  | 
  •  

Question

I seem to find quite a lot of code in the following situation:

class Thing
{
public:
   Thing() = default;
};

class Repo
{
public:
  Repo()
  {
     // Makes things..
     mThings.emplace_back( std::make_unique<Thing>() );
  } 

  // I find I need functions like this, 
  // a function which may return some record, 
  // or might return nullptr if there is no record.
  Thing* GetThing(int id)
  {
     // Might return nullptr, or might return
     return mThings[0].get();
  }

private:
  std::vector<std::unique_ptr<Thing>> mThings;
};

The object which uses Repo to grab a Thing doesn't own it, so if it OK/acceptable to use a raw pointer in this situation?

It seems like wrong to enforce it to be a std::shared_ptr and return a std::weak_ptr as the caller knows that if GetThing doesn't return nullptr then the object will live as long as it does. Plus the ownership isn't actually shared.

class SomeObj
{
public:
  SomeObj(Repo& repo, int id)
   : mRepo(repo), mMyThing(nullptr), mId(id)
  {
     mMyThing = mRepo.GetThing(mId);
  }
private:
  Repo& mRepo;
  Thing* mMyThing;
  int mId;
};
Was it helpful?

Solution

Yes, pure pointers are just enough. But objects that have a handle (pointer) to the Thing have to assume that the object being pointed to will outlive them.

OTHER TIPS

There is a working draft for std::optional and a boost library, which would do something similar to what you need:

// make sure to include <boost/optional/optional.hpp>

boost::optional<Thing&> GetThing(int id)
{
    // Might return nullptr, or might return
    return boost::optional<Thing&>(*mThings[0]);
}

The boost::optional class is supposed to cover cases like yours, where an object might be returned, but sometimes it will not. This has been covered in the past by using std::pair<bool, T> or using a pointer, if the object to be returned was a reference.

Using boost::optional makes semantics more clear, because you

  1. state that you won’t always return an object
  2. make clear, by returning a reference, that the receiver does not take ownership

The usage would be something like this:

Repo someRepo;
boost::optional<Thing&> someThing = someRepo;
if (someThing) {
    // use someThing just like a pointer
} else {
    // do not touch the value
}

(it might be possible to use someThing like a pointer directly; I have not tested this, I built this from the documentation)

Note: I’m not entirely sure whether the upcoming std::optional will support references. boost::optional does support them. Using a reference is kinda the point here though, because as the OP already noted in a comment on the question, having the bare type here would lead to copying, and using a raw pointer as T would kinda defeat the purpose.

@JohannesD commented a link to World’s dumbest smart pointer, which is made exactly for this purpose. I have no information about the status of this draft though.


However: Nowadays, I still tend to use raw pointers in that case, mainly because std::optional hasn’t hit implementations (and isn’t even fully specified and might not support references at all) yet. I am also a bit disappointed that it has been removed from C++14.

Make sure to make it clear in the documentation of that function that the receiver does not take ownership.

IMO, it's acceptable/OK as long as you NEVER want to use the code with multiple threads. If you might want to add multithreading in the future, it's a real problem, as the caller has no way of knowing how long the returned pointer will be valid, as another thread might do something that invalidates the pointer. In such cases a std::shared_ptr makes more sense (both in the data structure and as the return value).

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top