10

I have an object (Z) which derives from two other objects (A and B).

A and B both derive from enable_shared_from_this<>, respectively enable_shared_from_this<A> and enable_shared_from_this<B>.

Of course I call shared_from_this() on Z. And of course the compiler reports this as ambiguous.

My questions are :

  • is it safe to inherit twice from enable_shared_from_this<> or will it create two separated reference counts (bad !)
  • If not safe, how do I solve this ?

Note : I've found this other question bad weak pointer when base and derived class both inherit from boost::enable_shared_from_this but it doesn't really answer. Should I use the virtual trick too ?

curiousguy
  • 8,038
  • 2
  • 40
  • 58
Offirmo
  • 18,962
  • 12
  • 76
  • 97
  • 1
    possible duplicate of [boost shared\_from\_this and multiple inheritance](http://stackoverflow.com/questions/14939190/boost-shared-from-this-and-multiple-inheritance) – user Mar 11 '14 at 21:58

3 Answers3

15

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.

Community
  • 1
  • 1
ecatmur
  • 152,476
  • 27
  • 293
  • 366
  • @kassak `static_pointer_cast` won't work; this is virtual inheritance so a dynamic cast is required. – ecatmur Mar 21 '13 at 15:01
  • but that is simple downcasting from `virtual_enable_shared_from_this_base` to `T`, derived from it. Compiler complains? – kassak Mar 21 '13 at 15:10
  • @kassak yes: `cannot convert from base 'virtual_enable_shared_from_this_base' to derived type 'B' via virtual base 'virtual_enable_shared_from_this_base'` – ecatmur Mar 21 '13 at 15:32
  • It may not be the best to mention `std::enable_shared_from_this`. From what I can tell of the requirements, not using virtual inheritance is still supposed to work (which might be a defect). – Luc Danton Mar 21 '13 at 18:50
  • Virtual inheritance, like virtual functions, is much less efficient; it generates larger classes, larger vtables, and unreliable code in corner cases. It's very easy to get UB when using virtual bases during construction (arguably not a problem here as that base obviously can't be used during construction). – curiousguy Aug 31 '19 at 19:16
  • Why not to simply virtually derive from `enable_from_this`? Can you explain to me? – arsdever Apr 13 '20 at 17:00
  • 1
    @arsdever `enable_shared_from_this` is a template, so the two virtual base clases would be different and virtual inheritance wouldn't accomplish anything. – ecatmur Apr 14 '20 at 15:27
1

Yep, your class will be derived from two distinct classes enable_shared_from_this<A> and enable_shared_from_this<B>, and have two different weak ref's

Trick from that answer allows to have one base class, because of virtual inheritance

kassak
  • 3,974
  • 1
  • 25
  • 36
  • Why isn't this trick integrated in `enable_shared_from_this` by default ? – Offirmo Mar 21 '13 at 14:25
  • @LucDanton It can not deal, because both instatiations of enable shared from this are distinct classes. That is why even vrtually inherited they are distinct. The only way is to have one virtual class, which inherits it with any type argument, and then use that virtual class as base. The only problem can be, in `make_shared` but I'm not sure – kassak Mar 21 '13 at 14:35
  • @LucDanton thinking about it, using the "virtual" trick force the derived object to have a virtual table, thus making it bigger. Since C++ philosophy is "you only pay for what you ask", the virtual trick should not be used by default. – Offirmo Mar 21 '13 at 14:36
  • I made the mistake of thinking that this question was specifically about C++11 and `std::enable_shared_from_this`, apologies. – Luc Danton Mar 21 '13 at 18:51
0

Using the shared_ptr aliasing constructor, a variation of ecatmur's answer can be derived:

#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::shared_ptr<T>(
         virtual_enable_shared_from_this_base::shared_from_this(),
         static_cast<T*>(this));
   }
   std::shared_ptr<const T> shared_from_this() const {
      return std::shared_ptr<const T>(
         virtual_enable_shared_from_this_base::shared_from_this(),
         static_cast<const T*>(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();
}

I expect this version to be faster in many circumstances, since it avoids a costly dynamic cast. However, as usual, only benchmarks have the final word. Also, I have added the const variation.

Alexis Wilke
  • 19,179
  • 10
  • 84
  • 156
Giovanni Mascellani
  • 1,218
  • 2
  • 11
  • 26