In C++11, you can use a shared_ptr<>
to establish an ownership relation with an object or variable and weak_ptr<>
to safely reference that object in a non-owned way.
You can also use unique_ptr<>
to establish an ownership relation with an object or variable. But what if other, non-owning objects want to also reference that object? weak_ptr<>
isn't helpful in this case. Raw pointers are helpful but bring various downsides (e.g. they can be automatically initialized to nullptr but this is accomplished through techniques that are not consistent with the std::*_ptr<>
types).
What is the equivalent of weak_ptr<>
for non-owning references to objects owned via unique_ptr<>
?
Here's a clarifying example that resembles something in a game I'm working on.
class World
{
public:
Trebuchet* trebuchet() const { return m_trebuchet.get(); }
private:
std::unique_ptr< Trebuchet > m_trebuchet;
};
class Victim
{
public:
Victim( Trebuchet* theTrebuchet ) : m_trebuchet( theTrebuchet ) {}
~Victim()
{
delete m_trebuchet; // Duh. Oops. Dumb error. Nice if the compiler helped prevent this.
}
private:
Trebuchet* m_trebuchet; // Non-owning.
};
shared_ptr< Victim > createVictim( World& world )
{
return make_shared< Victim >( world.trebuchet() );
}
Here we use a raw pointer to maintain a non-owning relationship with an object owned via unique_ptr<>
elsewhere. But is raw the best we can do?
The hope is a type of pointer that:
- Looks like the other modern pointer types. E.g.
std::raw_ptr<T>
. - Replaces raw pointers so that a codebase that uses modern pointer types throughout can find all pointers via a search for
_ptr<
(roughly). - Auto-initializes to nullptr.
Thus:
int* p; // Unknown value.
std::raw_ptr< int > p; // null.
Does this type already exist in C++ now, is it proposed for the future, or is another implementation broadly available in e.g. Boost?
While you can't get a "weak" pointer to a uniquely owned object for free, the concept is useful and is used in a couple systems. See Chromium's WeakPtr and QT's QPointer for implementations.
Chromium's WeakPtr is implemented intrusively by storing a shared_ptr inside the weak-referenceable object and marking it invalid when the object is destroyed. WeakPtrs then reference that ControlBlock and check whether it's valid before handing out their raw pointer. I assume QT's QPointer is implemented similarly. Because ownership isn't shared, the original object is destroyed deterministically.
However, this means that dereferencing the
WeakUniquePtr
isn't thread-safe:Thread 1:
Thread2:
If line
A
happens to run concurrently with lineB
, thread 2 will wind up using a dangling pointer.std::weak_ptr
would prevent this problem by atomically taking a shared owning reference to the object before letting thread 2 use it, but that violates the assumption above that the object is owned uniquely. That means that any use of aWeakUniquePtr
needs to be synchronized with the destruction of the real object, and the simplest way to do that is to require that they're done in a message loop on the same thread. (Note that it's still completely safe to copy theWeakUniquePtr
back and forth across threads before using it.)One could imagine using a custom deleter in
std::unique_ptr
to implement this using standard library types, but that's left as an exercise for the reader.As Billy ONeal pointed out in his answer you likely want to pass a
Trebuchet&
instead of a pointer. The problem with the reference is that you cannot pass anullptr
,boost::optional
provides a way to have the equivilent of anullptr
. Further details on boost::optional are here: http://www.boost.org/doc/libs/1_54_0/libs/optional/doc/html/boost_optional/detailed_semantics.htmlSee also this question: boost::optional<T&> vs T*
Note:
std::optional<T>
is on track to make it into C++14 butstd::optional<T&>
is a separate proposal that is not in the current C++14 draft. Further details here: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3672.htmlThe "notify" behavior of
shared_ptr
requires reference counting the reference count control block.shared_ptr
's reference count control block(s) use separate reference counts for this.weak_ptr
instances maintain references to this block, andweak_ptr
s themselves prevent the reference count control block from beingdelete
ed. The pointed-to object has its destructor called when the strong count goes to zero (which may or may not result indelete
ion of the memory where that object was stored), and the control block isdelete
ed only when the weak reference count goes to zero.unique_ptr
's tenet is that it has zero overhead over a plain pointer. Allocating and maintaining reference count control blocks (to supportweak_ptr
-ish semantics) breaks that tenet. If you need behavior of that description, then you really want shared semantics, even if other references to the object are non-owning. There's still sharing going on in that case -- the sharing of the state of whether or not the object has been destroyed.If you need a generic nonowning reference and don't need notification, use plain pointers or plain references to the item in the
unique_ptr
.EDIT:
In the case of your example, it looks like
Victim
should ask for aTrebuchet&
rather than aTrebuchet*
. Then it's clear who owns the object in question.In the new C++ world with shared_ptr, weak_ptr, and unique_ptr you should not be storing long lived references to objects, like your trebuchet, using raw pointers or references. Instead World should have a shared_ptr to the trebuchet and Victim should store either a shared_ptr or a weak_ptr, depending on whether the trebuchet should stick around with the victim if the world goes away. Using a weak_ptr allows you to tell if the pointer is still valid (i.e. the world still exists), there is no way to do this with a raw pointer or reference.
When you use a unique_ptr you are declaring that only the World instance will own the trebuchet. Clients of the World class can use the World object's trebuchet by calling the "get" method but should not hold on to the reference or pointer returned by the method when they are done using it. Instead they should "borrow" the trebuchet every time they want to use it by calling the "get" method.
The above being said there could be instances where you want to store a reference or raw pointer for future use to avoid the overhead of the shared_ptr. But those instances are few and far between and you need to be completely sure that you won't use the pointer or reference after the World object that owns the trebuchet has gone away.
A function taking a raw pointer or reference implicitly promises not to hold on to a copy of that pointer after the function has returned. In return the caller promises that the pointer is valid (or
nullptr
) until the callee has returned.If you want to hold on to the pointer, you are sharing it (and should use
shared_ptr
). Aunique_ptr
manages a single copy of the pointer. You use raw pointers (or references) to refer to call functions involving that object.This is the same for
shared_ptr
objects.weak_ptr
only comes into play when you want to have an additional reference to the pointed too object that outlives the involved function. The main purpose of weak_ptr is to break reference cycles where two objects hold references to each other (and are therefore never released).Remember however that taking
shared_ptr
orweak_ptr
implies that the function taking that parameter will (optionally) modify some other object to retain a reference to the pointed to object that outlives the invocation of the function. In the vast majority of cases you use raw pointer (ifnullptr
is a valid value) or ref (when a value is guaranteed) even for shared_ptr or weak_ptr.There is a genuine need for a standard pointer type to act as a non-owning, inexpensive, and well-behaved counterpoint to
std::unique_ptr<>
. No such pointer has been standardized yet, but a standard has been proposed and is under discussion by the C++ standards committee. The "World's Dumbest Smart Pointer", akastd::exempt_ptr<>
would have the general semantics of other modern C++ pointer classes but would hold no responsibility either for owning the pointed-to object (asshared_ptr
andunique_ptr
do) or for correctly responding to the deletion of that object (asweak_ptr
does).Assuming that this feature is ultimately ratified by the committee, it would fully meet the need highlighted in this question. Even if it isn't ratified by the committee, the above linked document fully expresses the need and describes a complete solution.