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

后端 未结 8 1100
慢半拍i
慢半拍i 2020-12-01 03:59

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 t

相关标签:
8条回答
  • 2020-12-01 04:37

    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.

    0 讨论(0)
  • 2020-12-01 04:45

    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() );
    }
    
    0 讨论(0)
  • 2020-12-01 04:45

    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.

    0 讨论(0)
  • 2020-12-01 04:46

    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.

    0 讨论(0)
  • 2020-12-01 04:46

    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!!!
    
    0 讨论(0)
  • 2020-12-01 04:50

    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.

    0 讨论(0)
提交回复
热议问题