Search code examples
c++c++11shared-ptrunique-ptrweak-ptr

Referring to a "std::unique_ptr" that you don't own (use a raw pointer?)


Typically if you are using a std::shared_ptr to point to an object and you want to create another pointer to that object that does not share ownership you would create a std::weak_ptr.

// Create a shared pointer to own the object
std::shared_ptr<int> p = std::make_shared<int>(42);

// Create a weak pointer (that does not own the object)
std::weak_ptr<int> q(p);

// Use the weak pointer some time later
if (std::shared_ptr ptr = q.lock()) {
  // use *ptr
}

My question is, how do you do this when it comes to std::unique_ptr?

Using a unique pointer ensures that the current resource is owned exclusively by the std::unique_ptr itself. But what if I want to create a pointer to the same resource that does not own that resource? I can't use a std::weak_ptr because weak pointers are designed to work with the reference count from a std::shared_ptr. Would I just use a raw pointer here? Or is there a better alternative?

// Create a unique pointer to own the object
std::unique_ptr<int> p = std::make_unique<int>(42);

// Create a non-owning pointer to the same object
// Is this really the best way?
int* q = p.get();

// Use the pointer some time later
if (q != nullptr) {

  // Imagine this may be multithreaded...
  // what happens if p.reset() is called by another thread while the current thread is RIGHT HERE.

  // use *q
}

The only way I can think of creating a non-owning pointer to an object owned by a std::unique_ptr would be to use a raw pointer, but as you can see from the code above this may cause issues in threaded applications. Is there a better way to achieve the same goal?


Solution

  • Based on your last example, this is a scenario where std::shared_ptr and std::weak_ptr should be used.

    std::unique_ptr and a non-owning Raw Pointer should be used in the scenario where you have a guarantee that the smart pointer will outlive the raw pointer.

    class A {
        std::unique_ptr<int> ptr = std::make_unique<int>(5);
    public:
        int* get_ptr() const{return ptr.get();}
    };
    
    class B {
        A a;
    public:
        void do_something() {
            //int * ptr = a.get_ptr();//Valid, but not advised
            int & ref = *a.get_ptr();//Preferred
            ref++;
        }
    };
    

    If you can make this guarantee, you should be using std::unique_ptr and a raw pointer to represent this object. This is ideomatically correct.

    If, however, you can't guarantee lifetime at the time you need to manipulate the object, then references should be provided by std::weak_ptr, which are used to acquire ownership (even if only temporarily!) to make changes.

    class A {
        std::shared_ptr<int> ptr = std::make_shared<int>(5);
    public:
        std::weak_ptr<int> get_ptr() const {
            return ptr;//Implicitly converts
        }
        void destroy() {
            ptr.reset();
        }
    };
    
    class B {
        std::weak_ptr<int> ptr;
    public:
        B(std::weak_ptr<int> ptr) : ptr(ptr) {}
        void do_something() {
            if(auto owned_ptr = ptr.lock()) {//owned_ptr will be deduced to be of type std::shared_ptr<int>
                *owned_ptr++; //Guaranteed to only execute if the pointer is still valid
            }
        }
    };
    
    int main() {
        A a;
        B b(a.get_ptr());
        if(get_random_value() > 10)
            a.destroy();
        b.do_something();
    }