Is pairing weak_ptr to unique_ptr a good idea?

I know it sounds absurd to use weak_ptrs

with unique_ptrs

, but bear with me, please.

I have a set of widgets and animations that act on them. Widgets have a clear owner who creates and destroys them. All widgets are created, destroyed, animated in one thread, so one widget cannot be destroyed while the animation code is running. As you can see, the widgets are somehow shared with the animations, but the animation should stop if the widgets are removed.

The current approach is to use std::unique_ptr

widgets for owners and expose them as initial pointers to the animation. This makes it very difficult to find / debug dangling pointers. One suggestion was to change to std::shared_ptr

inside the owner class and expose std::weak_ptrs

for animations, but this would add some unwanted / unnecessary overhead on the system.

Is it possible (good idea?) To create some kind of weak_ptr on top std::unique_ptr

that just indicates that the pointer has been deleted? If so, can you please suggest me some minimal overhead implementations for single threaded use.

EDIT:

Another clarification - widgets are used in one thread, but the application has multiple threads. Also many animations run in parallel and each animation is updated 60 times / sec. The overhead from std::shared_ptr/std::weak_ptr

comes from the (atomic) counter used internally std::shared_ptr

, which is actually not needed in this particular case.

EDIT:

I am not asking if I can use std::weak_ptr

with std::unique_ptr

, I know it is not possible. I ask, is it a good idea / is it possible to build something with similar behavior like std::weak_ptr

that which can be coupled withstd::unique_ptr

+3


source to share


2 answers


No, you cannot use std::weak_ptr

with std::unique_ptr

. You do it std::shared_ptr

and expose std::weak_ptr

as you said.



As far as the overhead of link counting goes, I highly doubt this will be the bottleneck of your application, so profile and worry about it only when it does (probably never).

+2


source


Of course, this is a smart idea. It provides control over the lifetime of an object, giving slave threads the ability to detect when it has disappeared.

Of course, the method of lock()

your weak objects will have to return something that does not itself permit reuse.

You can do this by encapsulating existing shared_ptr and weak_ptr objects.

Simple example:



#include <iostream>
#include <memory>

// some type we're going to use for testing
struct Foo {
    ~Foo() {
        std::cout << "Foo destroyed" << std::endl;
    }

    void use() const {
        std::cout << "using Foo" << std::endl;
    }

};

// forward declaration
template<class T> struct weak_object_ptr;

// a pointer that keeps the object alive but is not itself copyable
template<class T>
struct keep_alive_ptr
{
    // make it moveable
    keep_alive_ptr(keep_alive_ptr&&) = default;
    keep_alive_ptr& operator=(keep_alive_ptr&&) = default;

    // provide accessors
    T& operator*() const {
        return *_ptr;
    }

    T* operator->() const {
        return _ptr.get();
    }

private:
    // private constructor - the only way to make one of these is to lock a weak_object_ptr
    keep_alive_ptr(std::shared_ptr<T> ptr)
    : _ptr { std::move(ptr) }
    {}

    // non-copyable
    keep_alive_ptr(const keep_alive_ptr&) = delete;
    keep_alive_ptr& operator=(const keep_alive_ptr&) = delete;

    friend weak_object_ptr<T>;

    std::shared_ptr<T> _ptr;
};

// a weak reference to our shared object with single point of ownership
template<class T>
struct weak_object_ptr
{
    weak_object_ptr(std::weak_ptr<T> w)
    : _weak { std::move(w) }
    {}

    keep_alive_ptr<T> lock() const {
        return keep_alive_ptr<T> { _weak.lock() };
    }
private:
    std::weak_ptr<T> _weak;
};

// a shared object store and lifetime controller
template<class T>
struct object_controller
{

    // helpful universal constructor
    template<class...Args>
    object_controller(Args&&...args)
    : _controller { std::make_shared<T>(std::forward<Args>(args)...) }
    {}

    weak_object_ptr<T> get_weak() const {
        return weak_object_ptr<T> { _controller };
    }

    void reset() {
        _controller.reset();
    }

private:
    std::shared_ptr<T> _controller;
};


// test

using namespace std;

int main(){
    auto foo_controller = object_controller<Foo> {};

    auto weak1 = foo_controller.get_weak();
    auto weak2 = foo_controller.get_weak();

    {
        auto strong1 = weak1.lock();
        strong1->use();
        cout << "trying to destroy Foo\n";
        foo_controller.reset();

        auto strong2 = weak2.lock();
        strong2->use();
        cout << "strong2 going out of scope\n";
    }

    return 0;
}

      

expected result (note that destruction of Foo is already legal):

using Foo
trying to destroy Foo
using Foo
strong2 going out of scope
Foo destroyed

      

0


source







All Articles