Using shared_from_this() without managed shared pointer in C++11
Asked Answered
N

5

5

Let's say I have a class which is a child class of enable_shared_from_this. The documentation of this base class says there should be a shared pointer which owns this class before calling shared_from_this. Is it safe to allocate the class with new and call shared_from_this to manage the object?

Nutbrown answered 10/12, 2015 at 22:43 Comment(0)
O
0

No, it's not safe. You should only call shared_from_this if the object is managed by a shared_ptr, not allocated via new (without an associated shared_ptr). For example this code

struct Test: std::enable_shared_from_this<Test> {
  std::shared_ptr<Test> getptr() {
    return shared_from_this();
  }
};

Test *test = new Test;
std::shared_ptr<Test> test2 = test->getptr();

will throw std::bad_weak_ptr (at least when using libstdc++). But this is OK:

std::shared_ptr<Test> test(new Test);
std::shared_ptr<Test> test2 = test->getptr();
Outfight answered 10/12, 2015 at 22:48 Comment(4)
What happens with test2 when I start managing a new pointer with test after the call to test->getptr(). Is this a safe use case? I'm asking this because boost::asio uses a lot of these constructs when starting async receive or write operations.Nutbrown
@Nutbrown You can't start managing the pointer after a call to getptr because the latter will throw unless it is already managed (by a shared_ptr).Outfight
I mean the following case: std::shared_ptr<Test> test(new Test); std::shared_ptr<Test> test2 = test->getptr(); test.reset(new AnotherTest); /* Do something with test2 */ Is this a valid use case?Nutbrown
Looks fine to me. reset simply replaces the managed object and it doesn't matter if it (or other instance) was obtained by shared_from_this or not.Outfight
P
6

As already mentioned by other users, calls to shared_from_this on instances that are not owned by shared_ptrs will result in an undefined behavior (usually an exception, but there are no guarantees).

So, why one more answer?

Because I did myself the same question once and got almost the same answer, then I started struggling with another question that immediately arose after that - how can I guarantee thus that all the instances are managed by a shared_ptr?

For the sake of completeness, I add another answer with a few details about this aspect.
Here a simple solution that had not been mentioned before.

So simple a solution, indeed: private constructors, factory method and variadic templates.
It follows a snippet that mixes all of them together in a minimal example:

#include<memory>
#include<utility>

class C: public std::enable_shared_from_this<C> {
    C() = default;
    C(const C &) = default;
    C(C &&) = default;
    C& operator=(const C &) = default;
    C& operator=(C &&c) = default;

public:
    template<typename... Args>
    static std::shared_ptr<C> create(Args&&... args) noexcept {
        return std::shared_ptr<C>{new C{std::forward<Args>(args)...}};
    }

    std::shared_ptr<C> ptr() noexcept {
        return shared_from_this();
    }
};

int main() {
    std::shared_ptr<C> c1 = C::create();
    std::shared_ptr<C> c2 = C::create(*c1);
    std::shared_ptr<C> c3 = c2->ptr();
    // these won't work anymore...
    // C c4{};
    // std::shared_ptr<C> c5 = std::make_shared<C>();
    // std::shared_ptr<C> c6{new C{}};
    // C c7{*c1};
    // ... and so on ...
}

The basic (trivial?) idea is to forbid the explicit construction of new instances, but by using the factory method here called create.
Variadic templates are used to avoid writing several factory methods, nothing more. Perfect forwarding helps us to do that the right way.

Pretty simple, isn't it?
Anyway it took me a while to figure out that, so I hope this will help future readers once across the same doubt.

Performance answered 6/2, 2016 at 23:6 Comment(1)
I think something like this should be a cpp core guideline.Submersible
L
2

From the standard:

§ 20.8.2.4

shared_ptr shared_from_this();

shared_ptr shared_from_this() const;

7 *Requires: enable_shared_from_this shall be an accessible base class of T. this shall be a subobject of an object t of type T. There shall be at least one shared_ptr instance p that owns &t.

8 Returns: A shared_ptr object r that shares ownership with p.

9 Postconditions: r.get() == this

If you call shared_from_this() within a class that is not managed by a shared_ptr the result will be undefined behaviour because you have not fulfilled one of the documented preconditions of the method.

I know from experience that in [the current version of] libc++ the result is an exception being thrown. However, like all undefined behavior this must not be relied upon.

Lenoir answered 10/12, 2015 at 22:58 Comment(0)
M
1

The documentation of this base class says there should be a shared pointer which owns this [object] before calling shared_from_this.

Okay, cool.

Is it safe to allocate the [object] with new and call shared_from_this to manage the object?

No. There should be a shared pointer which owns this [object] before calling shared_from_this.

Moise answered 10/12, 2015 at 23:6 Comment(0)
O
0

No, it's not safe. You should only call shared_from_this if the object is managed by a shared_ptr, not allocated via new (without an associated shared_ptr). For example this code

struct Test: std::enable_shared_from_this<Test> {
  std::shared_ptr<Test> getptr() {
    return shared_from_this();
  }
};

Test *test = new Test;
std::shared_ptr<Test> test2 = test->getptr();

will throw std::bad_weak_ptr (at least when using libstdc++). But this is OK:

std::shared_ptr<Test> test(new Test);
std::shared_ptr<Test> test2 = test->getptr();
Outfight answered 10/12, 2015 at 22:48 Comment(4)
What happens with test2 when I start managing a new pointer with test after the call to test->getptr(). Is this a safe use case? I'm asking this because boost::asio uses a lot of these constructs when starting async receive or write operations.Nutbrown
@Nutbrown You can't start managing the pointer after a call to getptr because the latter will throw unless it is already managed (by a shared_ptr).Outfight
I mean the following case: std::shared_ptr<Test> test(new Test); std::shared_ptr<Test> test2 = test->getptr(); test.reset(new AnotherTest); /* Do something with test2 */ Is this a valid use case?Nutbrown
Looks fine to me. reset simply replaces the managed object and it doesn't matter if it (or other instance) was obtained by shared_from_this or not.Outfight
S
0

This is not safe at all. Calling shared_from_this() from a non-shared_ptr invokes a bad_weak_ptr exception.

#include <iostream>
#include <memory>

struct A : std::enable_shared_from_this<A>
{
    A(A *parent, int x): parent(parent), x(x) {
        std::cout << "create A with " << x << std::endl;
    }

    void print(){
        std::cout << "print A : " << x << std::endl;
    }
    ~A(){
        std::cout << "delete A" << std::endl;
    }
    A *parent;
    int x;
};

void useA(const std::shared_ptr<A> &a) {
    a->print();
    a->parent->print();
    for(auto parent = a->parent; parent; parent = parent->parent){
        auto aptr = parent->shared_from_this();
        aptr->print();
    }
    
}

int main() {
    auto a1 = new A(NULL, 0);
    auto p1 = std::make_shared<A>(a1, 1);
    
    std::cout << "main" << std::endl;
    useA(p1);

}

In this example, std::bad_weak_ptr is thrown at

auto aptr = parent->shared_from_this();

Swaraj answered 16/8, 2022 at 8:52 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.