22

I just knew std::enable_shared_from_this form this link.
But after reading the code below, I don't know when to use it.

try {
        Good not_so_good;
        std::shared_ptr<Good> gp1 = not_so_good.getptr();
    } catch(std::bad_weak_ptr& e) {
        // undefined behavior (until C++17) and std::bad_weak_ptr thrown (since C++17)
        std::cout << e.what() << '\n';    
    }

The code above is "not so good" because there is no existing shared_ptr before calling getptr(). So the good thing should be:

std::shared_ptr<Good> gp1 = std::make_shared<Good>(); // having a shared_ptr at the beginning
std::shared_ptr<Good> gp2 = gp1->getptr();

However, if I have already had a shared_ptr object, why don't I just simply code like this: std::shared_ptr<Good> gp2 = gp1;, meaning that I don't need std::enable_shared_from_this at all.

In my opinion, using std::enable_shared_from_this is to make sure that more than one shared_ptr objects have the same control block so that we can avoid the double-delete problem. But if I must remind myself to create a shared_ptr at the beginning, why don't I just remind myself to use shared_ptr object to create a new one, instead of using raw pointer?

curiousguy
  • 8,038
  • 2
  • 40
  • 58
Yves
  • 11,597
  • 17
  • 83
  • 180
  • `void f(Good& g); /* ... */ f(*gp1); /* ... */ void f(Good& g) { /* now what? */ }` – milleniumbug Dec 28 '16 at 15:14
  • @milleniumbug sorry I don't understand what you are trying to say. – Yves Dec 28 '16 at 15:15
  • 1
    The `f` doesn't have the access to the `shared_ptr`, as it takes a reference, not a shared pointer. Sometimes you could change so it takes a reference instead, but not for member functions - `this` is always a raw pointer. – milleniumbug Dec 28 '16 at 15:21
  • @milleniumbug I think you misunderstand my question. My question is: using `std::enable_shared_from_this` doesn't save us from the risk of double delete. If so, `std::enable_shared_from_this` is useless. See the example: https://ideone.com/FlvIWw – Yves Dec 28 '16 at 15:44
  • 1
    Well you're wrong. `std::enable_shared_from_this` isn't supposed to be a protection from misusing the `shared_ptr`'s interface. For a use case of it, see my answer. – milleniumbug Dec 28 '16 at 15:54

3 Answers3

12

The hint about when std::enable_shared_from_this<T> is useful is in its name: when yielding objects based on some requests it may be necessary to return a pointer to an object itself. If the result should be a std::shared_ptr<T> it becomes necessary to return such a pointer from within a member function where there is generally no std::shared_ptr<T> accessible.

Having derived from std::enable_shared_from_this<T> provides a way to get hold of a std::shared_ptr<T> given just a pointer of type T. Doing so does, however, assume that the object is already managed via a std::shared_ptr<T> and it would create mayhem if the object is allocated on the stack:

struct S: std::enable_shared_from_this<S> {
    std::shared_ptr<S> get_object() {
        return this->shared_from_this();
    };
}

int main() {
    std::shared_ptr<S> ptr1 = std::make_shared<S>();
    std::shared_ptr<S> ptr2 = ptr1->get_object();
    // ...
}

In a realistic scenario there is probably some condition under which a std::shared_ptr<T> to the current object is returned.

dfrib
  • 70,367
  • 12
  • 127
  • 192
Dietmar Kühl
  • 150,225
  • 13
  • 225
  • 380
3

There're some use case which you can't use the template std::shared_ptr<T> like opaque pointer.

In that case, it's useful to have this:

In some_file.cpp

struct A : std::enable_shared_from_this<A> {};

extern "C" void f_c(A*);
extern "C" void f_cpp(A* a) {
   std::shared_ptr<A> shared_a = a->shared_from_this();
   // work with operation requires shared_ptr
}

int main()
{
    std::shared_ptr<A> a = std::make_shared<A>();
    f_c(a.get());
}

In some_other.c

struct A;
void f_cpp(struct A* a);
void f_c(struct A* a) {
    f_cpp(a);
}
Danh
  • 5,916
  • 7
  • 30
  • 45
  • i am kind of confused, why should i use sharea_ptr for a in f_cpp? if f_cpp required that, it should accept a shared_ptr instead of raw pointef A*. – Gavin Oct 12 '17 at 01:31
3

Let's say I want to represent a computation tree. We'll have an addition represented as a class deriving from expression with two pointers to expressions, so an expression can be evaluated recursively. However, we need to end the evaluation somewhere, so let's have numbers evaluate to themselves.

class Number;

class Expression : public std::enable_shared_from_this<Expression>
{
public:
    virtual std::shared_ptr<Number> evaluate() = 0;
    virtual ~Expression() {}
};

class Number : public Expression
{
    int x;
public:
    int value() const { return x; }
    std::shared_ptr<Number> evaluate() override
    {
        return std::static_pointer_cast<Number>(shared_from_this());
    }
    Number(int x) : x(x) {}
};

class Addition : public Expression
{
    std::shared_ptr<Expression> left;
    std::shared_ptr<Expression> right;
public:
    std::shared_ptr<Number> evaluate() override
    {
        int l = left->evaluate()->value();
        int r = right->evaluate()->value();
        return std::make_shared<Number>(l + r);
    }
    Addition(std::shared_ptr<Expression> left, std::shared_ptr<Expression> right) :
        left(left),
        right(right)
    {

    }
};

Live on Coliru

Note that the "obvious" way of implementing Number::evaluate() with return std::shared_ptr<Number>(this); is broken because it will result in double delete.

milleniumbug
  • 15,379
  • 3
  • 47
  • 71
  • The return type of `evaluate` should be `std::shared_ptr`, saving you from all the evil casting. – Ben Voigt Dec 28 '16 at 16:08
  • You forgot to specify the base class of `Number` and `Addition`. – Oktalist Dec 29 '16 at 16:23
  • @BenVoigt Yes, in this sample code one simply do that. My program had different types which is why I initially didn't do that. (it's also sad that I have to use a cast from `std::shared_from_this()` but I guess such is life) – milleniumbug Dec 31 '16 at 04:11
  • @Oktalist Fixed. Also added a link to coliru. – milleniumbug Dec 31 '16 at 04:12
  • You have to explicitly cast `shared_ptr` to `shared_ptr` for the same reason that you'd have to explicitly cast `Expression*` to `Number*` if you were using raw pointers. It's only unfortunate if the return type of `Expression::evaluate` is `shared_ptr` (which seems reasonable) that `shared_ptr` wouldn't qualify as a covariant return type. – Oktalist Dec 31 '16 at 14:42
  • I'd be *very* tempted to make all of `Expression`'s and `Number`s constructors `protected`, and provide factories – Caleth Sep 27 '18 at 09:49
  • I'd be very tempted not to use `shared_ptr` at all here. Use `unique_ptr`s, or pass by value - the code is simpler and double deletion isn't an issue. – user673679 Dec 21 '18 at 10:54