How to Enable_Shared_From_This of Both Parent and Derived

How to enable_shared_from_this of both parent and derived

Sorry, but there isn't.

The problem is that shared_ptr<foo> and shared_ptr<bar1> are different types. I don't understand everything that's going on under the hood, but I think that when the constructor returns and is assigned to a shared_ptr<foo>, the internal weak_ptr<bar1> sees that nothing is pointing to it (because only a shared_ptr<bar1> would increment the counter) and resets itself. When you call bar1::shared_from_this in get_callback, you get the exception because the internal weak_ptr isn't pointing to anything.

Essentially, enable_shared_from_this only seems to work transparently from a single class in a hierarchy. If you try implementing it manually, the problem should become obvious.

shared_from_this with derived class

It has nothing to do with inheritance. Call method this way instead will work: std::make_shared<foo_derived>()->method();

cppreference std::enable_shared_from_this::shared_from_this

It is permitted to call shared_from_this only on a previously shared
object
, i.e. on an object managed by std::shared_ptr. Otherwise the
behavior is undefined (until C++17)std::bad_weak_ptr is thrown (by the
shared_ptr constructor from a default-constructed weak_this) (since
C++17).

Use of enable_shared_from_this with multiple inheritance

Indeed you are doing it wrong. If you have simple inheritance, just inherit from enable_shared_from this in the base class, and derived class get it for free. (of course you'll need to downcast the result)

If you have multiple inheritance (like it seems), you must use the trick described here and also here :

/* Trick to allow multiple inheritance of objects
* inheriting shared_from_this.
* cf. https://stackoverflow.com/a/12793989/587407
*/

/* First a common base class
* of course, one should always virtually inherit from it.
*/
class MultipleInheritableEnableSharedFromThis: public std::enable_shared_from_this<MultipleInheritableEnableSharedFromThis>
{
public:
virtual ~MultipleInheritableEnableSharedFromThis()
{}
};

template <class T>
class inheritable_enable_shared_from_this : virtual public MultipleInheritableEnableSharedFromThis
{
public:
std::shared_ptr<T> shared_from_this() {
return std::dynamic_pointer_cast<T>(MultipleInheritableEnableSharedFromThis::shared_from_this());
}
/* Utility method to easily downcast.
* Useful when a child doesn't inherit directly from enable_shared_from_this
* but wants to use the feature.
*/
template <class Down>
std::shared_ptr<Down> downcasted_shared_from_this() {
return std::dynamic_pointer_cast<Down>(MultipleInheritableEnableSharedFromThis::shared_from_this());
}
};

Then your code becomes :

class A: public inheritable_enable_shared_from_this<A>
{
public:
void foo1()
{
auto ptr = shared_from_this();
}
};

class B: public inheritable_enable_shared_from_this<B>
{
public:
void foo2()
{
auto ptr = shared_from_this();
}
};

class C: public inheritable_enable_shared_from_this<C>
{
public:
void foo3()
{
auto ptr = shared_from_this();
}
};

class D: public A, public B, public C
{
public:
void foo()
{
auto ptr = A::downcasted_shared_from_this<D>();
}
};

Using std::enable_shared_from_this in base classes

In order not to expose shared_from_this you can make it protected (visible whithin whole hierarchy) or private (visible only inside the class) explicitly:

#include <memory>

class Foo : public std::enable_shared_from_this<Foo>
{
private:
using std::enable_shared_from_this<Foo>::shared_from_this;
};

Can't std::shared_from_this be inherited by its derived classes?

It is inherited:

// enable_shared_from_this example
#include <iostream>
#include <memory>

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

struct D : public C {};

int main () {
std::shared_ptr<D> foo;

foo = std::make_shared<D>();

std::shared_ptr<C> bar = foo->shared_from_this();

return 0;
}

You define the shared_from_this in the class C: so the object returns std::shated_ptr<C>. No surprise. You still may downcast it to std::shared_ptr<D> with std::dynamic_pointer_cast.

Using enable_shared_from_this in polymorphic inheritance with virtual destructor

Some minor points:

  • You could move the shared pointer into the lambda to avoid an atomic increment and decrement
  • No need to use a dynamic pointer cast since you know for sure the dynamic type (plus you don't check the result is not empty anyway!)
void MethodHandlerB::operator()(void* data){
auto thisPtr = std::static_pointer_cast<MethodHandlerB>(this->shared_from_this());
putLamdaToSomeGlobalEventThing([thisPtr = std::move(thisPtr)](){
thisPtr->doSomething();
});
}
  • Alternatively, you could use separate captures for this and the shared pointer, which avoids the cast altogether:
void MethodHandlerB::operator()(void* data){
putLamdaToSomeGlobalEventThing([this, thisPtr = shared_from_this()](){
doSomething();
});
}

Edit: as one of the comments points out, if you don't use shared_from_this() directly on the base class, you're better off just deriving from enable_shared_from_this in the derived classes. You can do this because C++ supports multiple inheritence.

class MethodHandlerBase {
public:
virtual void operator()(void* data) = 0;
virtual ~MethodHandlerBase(){}
};

class MethodHandlerA:
public MethodHandlerBase,
public std::enable_shared_from_this<MethodHandlerA>
{
private:
MethodHandlerACallback cb;
public:
MethodHandlerA(MethodHandlerACallback cb): cb(cb){}
virtual void operator()(void* data);
};

void MethodHandlerA::operator()(void* data){
putLamdaToSomeGlobalEventThing([self = shared_from_this()](){
self->doSomething();
});
}

Double inheritance of enable_shared_from_this

Yes, as per bad weak pointer when base and derived class both inherit from boost::enable_shared_from_this the solution is to use virtual inheritance. Here's an implementation for the C++11 standard shared_ptr (not Boost):

#include <memory>

struct virtual_enable_shared_from_this_base:
std::enable_shared_from_this<virtual_enable_shared_from_this_base> {
virtual ~virtual_enable_shared_from_this_base() {}
};
template<typename T>
struct virtual_enable_shared_from_this:
virtual virtual_enable_shared_from_this_base {
std::shared_ptr<T> shared_from_this() {
return std::dynamic_pointer_cast<T>(
virtual_enable_shared_from_this_base::shared_from_this());
}
};

struct A: virtual_enable_shared_from_this<A> {};
struct B: virtual_enable_shared_from_this<B> {};
struct Z: A, B { };
int main() {
std::shared_ptr<Z> z = std::make_shared<Z>();
std::shared_ptr<B> b = z->B::shared_from_this();
}

This isn't part of the default implementation, probably because of the overhead of virtual inheritance.

Is it OK to derive from std::enable_shared_from_this and an abstract base class?

Yes, it's absolutely fine.

shared_ptr has a templated constructor that takes a "convertible" pointer. And unless the shared_ptr constructor knows it is taking a Widget it doesn't know it derives from enable_shared_from_this and it can't store a weak_ptr.

Exactly right.

I just wonder if this behaviour is documented.

In the current standard enable_shared_from_this is very poorly specified, but see P0033 for the new and improved specification of enable_shared_from_this which will be in C++17. As well as answering the "what happens if you do it twice?" question, the revised wording specifies exactly how the enable_shared_from_this base class is used, and how the weak_ptr member is initialized. That part of the new wording is just standardising existing practice, i.e. it is just a more accurate description of what real implementations already do. (The answer to the "what happens if you do it twice?" question deviates from what implementations previously did, but for good reasons, and that isn't relevant to your question anyway).

The new spec clarifies that your original example is entirely well-defined and correct.

The current standard says that the modified version in your updated question has undefined behaviour when you call shared_from_this(), due to violating the precondition that there is shared_ptr that owns the pointer-to-derived (because you create a shared_ptr that owns the pointer-to-base). However, that precondition is not sufficient to ensure sensible semantics, as explained in the paper. The revised wording makes your modified version also well-defined (i.e. no undefined behaviour) but the weak_ptr in the base class will not share ownership with the shared_ptr<IWidget> and so shared_from_this() will throw an exception (which is what you observe from your implementation).

Must enable_shared_from_this be the first base class?

When ~A() and ~B() run, can I be sure that the storage where C lived
is still present?

Of course! It would be hard to use a base class that tries to free its own memory (the memory where it resides). I'm not sure it's even formally legal.

Implementations don't do that: when a shared_ptr<T> is destructed or reset, the reference count (RC) for the shared ownership of T is decremented (atomically); if it reached 0 in the decrement, then destruction/deletion of T is started.

Then the weak-owners-or-T-exists count is decremented (atomically), as T no longer exists: we need to know if we are the last entity standing interested in the control block; if the decrement gave a non zero result, it means some weak_ptr exist that share (could be 1 share, or 100%) ownership of control block, and they are now responsible for the deallocation.

Either way, atomic decrement will at some point end up with a zero value, for the last co-owner.

Here there are no threads, no non-determinism, and obviously the last weak_ptr<T> was destroyed during destruction of C. (The unwritten assumption in your question being that no other weak_ptr<T> was kept.)

Destruction always happen in that exact order. The control block is used for destruction, as no shared_ptr<T> knows (in general) which (potentially non virtual) destructor of (potentially different) most derived class to call. (The control block also knows not to deallocate memory on shared count reaching zero for make_shared.)

The only practical variation between implementations seems to be about the fine details of memory fences and avoiding some atomic operations in common cases.



Related Topics



Leave a reply



Submit