Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

I am writing a class that should derive from an abstract base class. I cannot change the abstract base class. The class will be held as a shared_ptr to the abstract base class. Is it OK to inherit from the abstract base class and enable_shared_from_this? Like this:

class IWidget {
public:
  virtual ~IWidget(){}
  // ...
};

class Widget : public std::enable_shared_from_this<Widget>, public IWidget {
protected:
  Widget();  // protected, use create
public:
  static std::shared_ptr<IWidget> create() {
    return std::shared_ptr<IWidget>(new Widget(init));
  }
  // ...
};

More complete code here that seems to work.

Most of the examples I can find of enable_shared_from_this have it on the base class. In this case I can't change the base class. Is it OK to use multiple inheritance and use it on the derived class?

I was a bit worried that I could only guarantee enable_shared_from_this would only work if I created a shared_ptr<Widget> but in this case I'm creating a shared_ptr<IWidget>.

Update: One interesting thing I noticed is that if I change the create method to:

  IWidget* w = new Widget(init);
  return std::shared_ptr<IWidget>(w);

I get a runtime error when I try and use shared_from_this(). I think this makes sense. 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. I just wonder if this behaviour is documented.

like image 219
user3467895 Avatar asked Dec 19 '15 15:12

user3467895


1 Answers

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).

like image 107
Jonathan Wakely Avatar answered Sep 29 '22 16:09

Jonathan Wakely