Click here to Skip to main content
15,881,898 members
Articles / Programming Languages / C++

Solution for Multiple enable_shared_from_this in Inheritance Tree

Rate me:
Please Sign up or sign in to vote.
4.73/5 (5 votes)
21 Nov 2011MIT5 min read 45.8K   377   9   4
The initial implementation of enable_shared_from_this of STL or the one from Boost C++ causes crash if it appears more than once in the inheritance tree of a user class. This is a solution for the problem. (search tag: enable shared from this)

Introduction

Smart Pointers in the Boost C++ libraries and now in C++11 offer a facility to retrieve a shared_ptr from the this pointer in a member function. This is done via the enable_shared_from_this template class. A good example of the necessity of having a shared_ptr from this is when you want to call a function func inside a member function, and you need to pass this in argument to the function func. By passing this as a raw pointer, you would break the rule of not mixing shared_ptr and raw pointer to the same memory block, negating the shared_ptr usefulness. Using enable_shared_from_this, you can send a shared_ptr from this to the function func, ensuring only shared_ptr to the object allocation space are used in your program.

The enable_shared_from_this template class has a member weak_ptr to this and a method, shared_from_this that returns a shared_ptr to this by locking that weak_ptr. The way the weak_ptr is initialized is in the shared_ptr constructor, by using a clever trick to statically determines if the object being wrapped inherits from enable_shared_from_this.

The problem with this is that it assumes that there is only one weak_ptr to initialize. What if enable_shared_from_this appears more than one time in the inheritance tree, like the following:

esft1.png

In a situation like this, either one of the enable_shared_from_this' weak_ptr or none of them will be initialized, depending on the compiler (yes it is true, different compilers behave differently for this!). So when an object of type Derived tries to access its shared_from_this through Base (e.g. in a method of Base) and/or in a method of Derived, it won't be initialized to this even though a shared_ptr for the class was instantiated. Typically, one assumes that shared_from_this is always initialized as long as Derived is instantiated via shared_ptr, but in that situation, it is not the case and any access to it will cause a segmentation fault.

This article proposes a solution to initialize all the enable_shared_from_this' weak_ptr in an inheritance tree.

Background

If you have a crash after trying to indirect a shared_from_this(), and you are sure that all the objects of your class are pointed by shared_ptr, then verify the inheritance tree of the class. If you have more than one enable_shared_from_this in the tree, then there is your problem.

This happened to me because I have implemented a SharedObserver pattern (Observer pattern that uses shared_ptr). In the pattern, I send the Subject to the Observer via a shared_from_this() in the notify method of the Subject. Therefore, SharedSubject needed to inherit from enable_shared_from_this. One of my concrete Subjects in my program also needed to use shared_from_this(), thus inheriting a second time from enable_shared_from_this and there goes the crash and the investigation that lead me to this solution.

Using the Code

First, I needed to modify slightly the enable_shared_from_this, so I created a class EnableSharedFromThis. Therefore, make your class inherit from EnableSharedFromThis instead of enable_shared_from_this.

C++
class Base : public EnableSharedFromThis< Base >
{
public:
  Base(int iValue) : mValue(iValue) {}
  void BaseFunc()
  {
    FuncBase(EnableSharedFromThis< Base >::SharedFromThis());
  }
  void PrintValue()
  {
    std::cout << "Value = " << mValue << std::endl;
  }
private:
  int mValue;
};

class Derived : public Base, public EnableSharedFromThis< Derived >
{
public:
  Derived(int iValue) : Base(iValue) {}
  void DerivedFunc()
  {
    FuncDerived(EnableSharedFromThis< Derived >::SharedFromThis());
  }
};

Here is the implementation of FuncBase and FuncDerived, two free functions that receive respectively a shared_ptr of Base and one of Derived. They are called inside a member function of Base and Derived respectively. Both member functions pass the corresponding SharedFromThis as argument to the free functions.

C++
void FuncBase(std::shared_ptr< Base > iBase)
{
  iBase->PrintValue();
}

void FuncDerived(std::shared_ptr< Derived > iDerived)
{
  iDerived->PrintValue();
}

Then I created a SmartPtrBuilder with a static template method CreateSharedPtr that you can call, specifying a variable number of template argument, one for each class that inherits from EnableSharedFromThis that needs to be initialized.

C++
// You must specify all the classes that inherits from EnableSharedFromThis
// in Derived inheritance tree as template argument
std::shared_ptr< Derived > wDerived = SmartPtrBuilder::CreateSharedPtr
    < Derived, Base >(new Derived(123));

Then you can use wDerived normally and all its internal weak_ptrs will be initialized.

C++
// One or both of the following would crash if using the original 
// std::enable_shared_from_this
wDerived->BaseFunc();
wDerived->DerivedFunc();

Points of Interest

Factory Method

There is a good way not to force users of your classes to instantiate them using SmartPtrBuilder::CreateSharedPtr and thus not making these users responsible to correctly specify the correct types to pass as template argument everywhere. You can simply provided them with a factory method that hides all those details, like the following:

C++
class Derived : public Base, public EnableSharedFromThis< Derived >
{
public:
  // Factory method
  static std::shared_ptr< Derived > Create(int iValue)
  {
    return SmartPtrBuilder::CreateSharedPtr< Derived, Base >(new Derived(iValue));
  }
  void DerivedFunc()
  {
    FuncDerived(EnableSharedFromThis< Derived >::SharedFromThis());
  }
private:
  Derived(int iValue) : Base(iValue) {}
};

And instantiate Derived like the following:

C++
// Simply use the factory method to instantiate Derived
std::shared_ptr< Derived > wDerived = Derived::Create(123);

Other Thoughts

Another good solution for this whole enable_shared_from_this problem exists, but involves dynamic_cast. It is possible to create a polymorphic base class for the template class enable_shared_from_this. The weak_ptr is actually inside the polymorphic base class and when shared_from_this method is called, a dynamic_cast of the weak_ptr of the polymorphic base class is done, ensuring a proper type for the returned shared_ptr. However, since the solution involves dynamic_cast, the performance impact might be not negligible. Also RTTI would be required and it is not always possible to use it, like in some RTOS environment, making this solution not universal. By the way, static_cast instead of dynamic_cast in that situation would not always compile, e.g. if you use virtual inheritance ; this is why we are forced to use dynamic_cast as a generic solution.

As food for thought, if a way of exploring or inspecting an inheritance tree statically (at compile time) were possible, then a solution where you do not have to provide all the classes that inherit from enable_shared_from_this upon construction of the shared_ptr would be possible. But even if it is theoretically possible to implement this feature in a C++ compiler, I don't think it is done in the current version of the standard (C++11 at the moment of writing this article).

Also, I have created a variadic template version of the SmartPtrBuilder, if you want it, please ask in the comment below or contact me.

History

Version 1.0

The zip file contains the original EnableSharedFromThis template class, the SmartPtrBuilder and a usage example as a VS2010 solution file (you can use VS2010 Express freely available here). For GCC, you just have to build the example and run it like this:

$ g++ -std=c++0x main.cpp -o esft
$ ./esft

License

This article, along with any associated source code and files, is licensed under The MIT License


Written By
Software Developer (Senior) CAE Inc.
Canada Canada
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
QuestionSuppose the following: Pin
Giuseppe Recalcati6-Jun-13 6:10
Giuseppe Recalcati6-Jun-13 6:10 
AnswerRe: Suppose the following: Pin
Giuseppe Recalcati6-Jun-13 6:39
Giuseppe Recalcati6-Jun-13 6:39 
Suggestionvirtual inheritance Pin
coder2555-Nov-12 15:27
coder2555-Nov-12 15:27 
AnswerRe: virtual inheritance Pin
Philippe Cayouette5-Nov-12 18:40
Philippe Cayouette5-Nov-12 18:40 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.