2

I have next code:

class A 
{
    virtual ~A() = default;
    virtual void foo1() = 0;
};
class B
{
    virtual ~B() = default;
    virtual void foo2() = 0;
};

class C: public A, public std::enable_shared_from_this<C>, public B
{
    void foo1() override
    {
    }

    void foo2() override
    {
    }
};

Is it correct that class std::enable_shared_from_this goes before class B? Does it matter where std::enable_shared_from_this is?

Igor
  • 1,029
  • 9
  • 21
  • 3
    The primary problem in this example is the lack of virtual destructor. – user7860670 Apr 21 '20 at 09:36
  • 1
    @user7860670 it's fine, since `std::shared_ptr` calls the destructor for the dynamic type. – Quentin Apr 21 '20 at 09:41
  • @Quentin [It does not](https://godbolt.org/z/BT_re2). And even if did, object is not necessary owned by `shared_ptr`. – user7860670 Apr 21 '20 at 09:48
  • 1
    @user7860670 yeah, it does not when you go out of your way to break it... – Quentin Apr 21 '20 at 09:50
  • @Quentin It does not because it is not supposed to unless you go and purposely make it aware of the object's dynamic type... – user7860670 Apr 21 '20 at 10:03
  • 1
    @user7860670 - *"unless you go and purposely make it aware of the object's dynamic type"* Out of your way as in following good practice and using `std::make_shared`? Your'e right, who is ever gonna do *that* – StoryTeller - Unslander Monica Apr 21 '20 at 10:06
  • 1
    @user7860670 both `std::make_shared` and the conversion from `std::unique_ptr` type-erase the destructor with no further action required, and that covers all of the normal use cases. It does not, cannot, and should not jump through hoops to try and retrieve it when you willingly work around it. – Quentin Apr 21 '20 at 10:07
  • Does this answer your question? [C++ multiple inheritance order](https://stackoverflow.com/questions/17328921/c-multiple-inheritance-order) – KamilCuk Apr 21 '20 at 10:08
  • `Is it correct that class std::enable_shared_from_this goes before class B?` yes `Does it matter where std::enable_shared_from_this is?` It does not matter in your example. – KamilCuk Apr 21 '20 at 10:09
  • @StoryTeller-UnslanderMonica Use of "std::make_shared" falls under "purposely make it aware of the object's dynamic type". – user7860670 Apr 21 '20 at 10:10
  • @Quentin [Only conversion from std::unique_ptr parametrized with the derived type](https://godbolt.org/z/AkMU_r)... – user7860670 Apr 21 '20 at 10:13
  • 2
    @user7860670 - Duh, that was my point. You make it sound like the correct usage is esoteric and unlikely to appear in practice. Whereas it is your own example that is entirely manufactured and disjoint from the overwhelmingly common case. – StoryTeller - Unslander Monica Apr 21 '20 at 10:13
  • 1
    @user7860670 yes, because `std::unique_ptr` does *not* erase the destructor, thus your `p_unique` is invalid already. – Quentin Apr 21 '20 at 10:14
  • @StoryTeller-UnslanderMonica The thing is that statements "std::shared_ptr calls the destructor for the dynamic type" and something like "common use cases of std::shared_ptr imply that it will be aware of the object's dynamic type and will invoke proper destructor helping you to dodge the bullet" are fundamentally different. The former (posted by Quentin) is misleading. "You make it sound like the correct usage is esoteric and unlikely to appear in practice." - i do not. However according to the Murphy's law incorrect usage is almost guaranteed to appear in practice. – user7860670 Apr 21 '20 at 10:36
  • 2
    @user7860670 I fail to see your point. `std::shared_ptr` has been designed to type-erase the destructor so it doesn't need to be virtual, that's it. And it does so as long as you use it normally. Acting like a feature doesn't exist at all if you can write code that breaks it won't get anywhere, especially in C++. – Quentin Apr 21 '20 at 10:39
  • @Quentin My point is that "std::shared_ptr calls the destructor for the dynamic type" is misleading. You can not use something like "And it does so as long as you use it normally" to support that first statement. Use of `shared_ptr` in the manner that prevents it from figuring out object's dynamic type and from invoking destructor for the dynamic type is not prohibited or invalid on its own in any way. – user7860670 Apr 21 '20 at 11:06
  • @user7860670 using the library as designed is a prerequisite for literally every feature, I'm not going to tack "unless you write a bug" onto the end of every sentence. And yes, causing a `std::shared_ptr` with a default deleter to deduce the wrong dynamic type is invalid by definition since it triggers UB later on. – Quentin Apr 21 '20 at 11:12
  • @user7860670 `std::shared_ptr` is designed to handle types with non-polymorphic destructors. It's a feature. It's specified by the standard. No amount of made-up broken code will make it disappear. If you dislike it so badly that you need to complain and pretend it doesn't exist, please go and write a standard proposal to remove it instead of bothering me for bringing it up. – Quentin Apr 21 '20 at 12:20

2 Answers2

3

Does it matter where std::enable_shared_from_this is?

Technically it doesn't matter.

The order of derivation affects the order of constructor and destructor invocation.

  • From a construction perspective, enable_shared_from_this<T> constructor is a no-op; it simply adds a value-initialized, i.e. empty, weak_ptr-to-self to the class, and is detected by shared_ptr<T> constructor, which assigns itself to that member right after the object is fully constructed.

  • From a destruction perspective it also doesn't matter even if a destructor of another base before or after it throws. The destructor will still be invoked and the weak counter of the corresponding shared_ptr, if any, will be properly decremented, allowing it to be freed when the time comes (for the really curious, the exception cleanup rules can be found here).

It might matter from a code style perspective, however. For example, the following declaration is arguably more readable:

class C: public A, public B, public std::enable_shared_from_this<C>
. . .
rustyx
  • 80,671
  • 25
  • 200
  • 267
0

It does not matter.

The order of base classes may affect:

  • Construction order (first base first, except for virtual inheritance)
  • Memory layout order (not deterministic, may not affect)

So, only construction order matters.

But you cannot use shared_from_this in a base class constructor/destructor anyway.

You cannot use it even in body of constructor or destructor of current class, as it will throw exception until it is placed inside shared_ptr<C>, or shared_ptr<D>, whatever. And you don't even have an easy way to attempt shared_from_this from base class, as you cannot call virtual functions from constructor of base class the way that the derived implementation is called.

So it does not matter at all.

Alex Guteniev
  • 12,039
  • 2
  • 34
  • 79