Gustavo
Gustavo

Reputation: 153

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

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?

Upvotes: 5

Views: 1473

Answers (5)

ugitho
ugitho

Reputation: 11

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();

Upvotes: 0

skypjack
skypjack

Reputation: 50568

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.

Upvotes: 6

Lightness Races in Orbit
Lightness Races in Orbit

Reputation: 385405

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.

Upvotes: 1

Richard Hodges
Richard Hodges

Reputation: 69942

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.

Upvotes: 2

vitaut
vitaut

Reputation: 55766

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();

Upvotes: 0

Related Questions