Will move from unique to shared pointer also initialize enable_shared_from_this

Viewed 323

When I inherit from std::enable_shared_from_this, but I create a unique_ptr, will the weak_ptr inside std::enable_shared_from_this also be initialized when I "move" to a shared_ptr by std::move, or by the move constructor?

For example what will happen in the below code:

#include <memory>
#include <iostream>

class A : public std::enable_shared_from_this< A >
{
public:
    std::shared_ptr< A > getA()
    {
        return shared_from_this();
    }
};

int main()
{
    std::unique_ptr< A > u(new A());
    // Aborts
    //std::cout << u->getA() << std::endl;
    std::shared_ptr< A > s(std::move(u));
    // Will this work or abort too?
    std::cout << s << ", " << s->getA() << std::endl;
}
3 Answers

According to https://en.cppreference.com/w/cpp/memory/enable_shared_from_this all the std::shared_ptr constructors should initialise the internal weak reference so moving a std::unique_ptr into a std::shared_ptr should enable the use of shared_from_this.

You do have to be careful that nobody calls shared_from_this whilst the object is owned by std::unique_ptr as that is either undefined behaviour or will throw std::bad_weak_ptr depending on your c++ version.

[util.smartptr.shared.const]/1 In the constructor definitions below, enables shared_from_this with p, for a pointer p of type Y*, means that if Y has an unambiguous and accessible base class that is a specialization of enable_shared_from_this (23.11.2.5), then remove_cv_t<Y>* shall be implicitly convertible to T* and the constructor evaluates the statement:

if (p != nullptr && p->weak_this.expired())
p->weak_this = shared_ptr<remove_cv_t<Y>>(*this, const_cast<remove_cv_t<Y>*>(p));

template <class Y, class D> shared_ptr(unique_ptr<Y, D>&& r);

[util.smartptr.shared.const]/29 Effects: ... equivalent to shared_ptr(r.release(), r.get_deleter())


template<class Y, class D> shared_ptr(Y* p, D d);

[util.smartptr.shared.const]/10 Effects: Constructs a shared_ptr object that owns the object p and the deleter d. When T is not an array type, ... enable shared_from_this with p

So yes, std::shared_ptr< A > s(std::move(u)); does initialize things appropriately to make shared_from_this work.

As pointed out by Alan and Igor, the std::weak_ptr<> implementing the shared_from_this() magic is set appropriately whenever std::shared_ptr<> is constructed to hold an object publicly derived from std::enable_shared_from_this. So, yes you code will work.

However, the member method

std::shared_ptr<A> A::getA()
{
    return shared_from_this();
}

will envoke UB (prior to C++17) unless the object is actually managed by a std::shared_ptr<>. Unfortunately, prior to C++17, there is no way to tell from within the object (of type A) whether it is managed by a shared pointer, and you cannot easily prevent this UB.

Since you're actually using std::unique_ptr<> to manage your objects, I recommend therefore to not inherit from std::enable_shared_from_this.

Related