shared_ptr<> is to weak_ptr<> as unique_ptr<> is to... what?

C++C++11Unique Ptr

C++ Problem Overview


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?

C++ Solutions


Solution 1 - C++

The "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, and weak_ptrs themselves prevent the reference count control block from being deleteed. The pointed-to object has its destructor called when the strong count goes to zero (which may or may not result in deleteion of the memory where that object was stored), and the control block is deleteed 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 support weak_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 a Trebuchet& rather than a Trebuchet*. Then it's clear who owns the object in question.

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;     // Compiler error. :)
    }

private:

    Trebuchet& m_trebuchet;    // Non-owning.
};

shared_ptr< Victim > createVictim( World& world )
{
    return make_shared< Victim >( world.trebuchet() );
}

Solution 2 - C++

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", aka std::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 (as shared_ptr and unique_ptr do) or for correctly responding to the deletion of that object (as weak_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.

Solution 3 - C++

unique_ptr's non-owing analog is a plain C pointer. What is different - C pointer doesn't know if the pointed data is still accessible. weak_ptr on the other hand does. But it is impossible to replace raw pointer with a pointer knowing about the validity of data without additional overhead (and weak_ptr does have that overhead). That implies C-style pointer is the best in terms of speed you can get as a non-owing analog for unique_ptr.

Solution 4 - C++

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:

unique_ptr<MyObject> obj(new MyObject);
thread2.send(obj->AsWeakPtr());
...
obj.reset();  // A

Thread2:

void receive(WeakUniquePtr<MyObject> weak_obj) {
  if (MyObject* obj = weak_obj.get()) {
    // B
    obj->use();
  }
}

If line A happens to run concurrently with line B, 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 a WeakUniquePtr 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 the WeakUniquePtr 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.

Solution 5 - C++

boost::optional<Trebuchet&>

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 a nullptr, boost::optional provides a way to have the equivilent of a nullptr. Further details on boost::optional are here: http://www.boost.org/doc/libs/1_54_0/libs/optional/doc/html/boost_optional/detailed_semantics.html

See also this question: https://stackoverflow.com/questions/17007949/boostoptionalt-vs-t

Note: std::optional<T> is on track to make it into C++14 but std::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.html

Solution 6 - C++

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.

Solution 7 - C++

otn::raw::weak (from C++ Object Token Library) is non-owning, inexpensive, and well-behaved counterpoint to std::unique_ptr. Also in the library there is otn::safe::unique, a unique owner which can "notify" a non-owning otn::safe::weak about the deletion of the object.

#include <otn/all.hpp>
#include <iostream>

int main()
{
    using namespace std;
    using namespace otn;

    raw::weak_optional<int> raw_weak;
    if (!raw_weak)
        cout << "raw_weak is empty" << endl;

    cout << "--- create object in std_unique..." << endl;
    auto std_unique = std::make_unique<int>(42);
    raw_weak = std_unique;
    if (std_unique)
        cout << "std_unique is not empty" << endl;
    if (raw_weak)
        cout << "raw_weak is not empty" << endl;

    cout << "--- move std_unique to safe_unique..." << endl;
    safe::unique_optional<int> safe_unique = std::move(std_unique);

    if (!std_unique)
        cout << "std_unique is empty" << endl;
    if (raw_weak)
        cout << "raw_weak is not empty, it is observs safe_unique" << endl;

    safe::weak_optional<int> safe_weak = safe_unique;
    if (safe_unique)
        cout << "safe_unique is not empty" << endl;
    if (!safe_weak.expired())
        cout << "safe_weak is not expired" << endl;

    cout << "--- destroy object in safe_unique..." << endl;
    utilize(std::move(safe_unique));
    if (!safe_unique)
        cout << "safe_unique is empty" << endl;
    if (safe_weak.expired())
        cout << "safe_weak is expired, it is not dangling" << endl;
    if (raw_weak)
        cout << "raw_weak is not empty, it is dangling!!!" << endl;
}

Output:

raw_weak is empty
--- create object in std_unique...
std_unique is not empty
raw_weak is not empty
--- move std_unique to safe_unique...
std_unique is empty
raw_weak is not empty, it is observs safe_unique
safe_unique is not empty
safe_weak is not expired
--- destroy object in safe_unique...
safe_unique is empty
safe_weak is expired, it is not dangling
raw_weak is not empty, it is dangling!!!

Solution 8 - C++

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). A unique_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 or weak_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 (if nullptr is a valid value) or ref (when a value is guaranteed) even for shared_ptr or weak_ptr.

Solution 9 - C++

now you hava noshared_ptr --

https://github.com/xhawk18/noshared_ptr

noshared_ptr<T> -- a new type of unique ptr
noweak_ptr<T>   -- weak ptr for the noshared_ptr

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionOldPeculierView Question on Stackoverflow
Solution 1 - C++Billy ONealView Answer on Stackoverflow
Solution 2 - C++OldPeculierView Answer on Stackoverflow
Solution 3 - C++sasha.sochkaView Answer on Stackoverflow
Solution 4 - C++Jeffrey YasskinView Answer on Stackoverflow
Solution 5 - C++ChetSView Answer on Stackoverflow
Solution 6 - C++Brett HallView Answer on Stackoverflow
Solution 7 - C++ViTechView Answer on Stackoverflow
Solution 8 - C++Paul de VriezeView Answer on Stackoverflow
Solution 9 - C++xhawk18View Answer on Stackoverflow