1

The examples I've found of enable_shared_from_this show it used via inheritance. For example:

struct Good : enable_shared_from_this<Good> {
    shared_ptr<Good> getptr() {
        return shared_from_this();
    }
};

int main() {
    // Good: the two shared_ptr's share the same object
    shared_ptr<Good> gp1(new Good);
    shared_ptr<Good> gp2 = gp1->getptr();
    cout << "gp2.use_count() = " << gp2.use_count() << endl;
}

I've been warned a lot in my day about the dangers of inheriting from the standard library. This code certainly seems to share those dangers, for example:

struct A : enable_shared_from_this<A> {};
struct B : enable_shared_from_this<B> {};

If I want to create struct C : A, B {}; the sticking point would obviously be C::shared_from_this(). Obviously we can work around this, but there is some inherent complexity.

So my question is, is there a way to use enable_shard_from_this as a has-a relationship instead of an is-a relationship?

Jonathan Mee
  • 37,899
  • 23
  • 129
  • 288
  • 2
    Regarding point 3: there isn't a `enable_shared_from_this`; there is `enable_shared_from_this`. This is important. If `A` derives from `enable_shared_from_this` and `B` derives from `enable_shared_from_this` then they are different base classes. – Simple Dec 02 '15 at 12:33
  • This is called the [CRTP](https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern). – Simple Dec 02 '15 at 12:34
  • @Simple That's the sticking point. What does `shared_from_this` reference? You have to specify. I'm not claiming that any of these difficulties are insurmountable, they just add complexity to the code that wouldn't be there if it weren't for `enable_shared_from_this` inheritance. – Jonathan Mee Dec 02 '15 at 12:36
  • I don't understand what point you are making. There is no difficulty WRT point 3 (the only point I have discussed) as it is a non-issue. – Simple Dec 02 '15 at 12:40
  • @Simple I'm simply claiming it adds complexity, in the child class one can no longer call `shared_from_this()` they must now know whether to call `A::shared_from_this()` or `B::shared_from_this()` this difficulty is compounded if the `shared_ptr` is returned. How can I know internally which type of `shared_ptr` the caller wanted a `shared_ptr` or a `shared_ptr`? Again, not insurmountable, it simply adds complexity. – Jonathan Mee Dec 02 '15 at 12:46
  • 1
    It's not a common problem in practice. If it's a problem it suggests the complexity is already present: you're overusing `enable_shared_from_this` or your class hierarchy should be changed to have a single base class that provides the `shared_from_this` functionality. – Jonathan Wakely Dec 02 '15 at 12:49
  • I think you should learn how `shared_ptr` and `enable_shared_from_this` are implemented so you know why they are the way they are. You need to be able to construct a `shared_ptr` from an already existing `T`; having `enable_shared_from_this` as a base class means the `shared_ptr` constructor can store a `weak_ptr` inside of `T`. – Simple Dec 02 '15 at 12:52
  • 1
    The improved specification in http://kayari.org/cxx/enable_shared_from_this.html might help understand it, without reading actual implementations. – Jonathan Wakely Dec 02 '15 at 12:55
  • 2
    1. The destructor is protected, so it doesn't matter that it's not virtual. No one can call it directly anyway. 2. The destructor doesn't destroy `*this`. It cannot. 3. If you see a sticking point, please point out where exactly it lies. – n. m. could be an AI Dec 02 '15 at 13:08
  • @Simple I tried to clean up the question to better illiterate what I'm trying to say. But as I look at it I think I agree with [Jonathan Wakely's comment](http://stackoverflow.com/questions/34042589/can-enable-shared-from-this-be-used-without-inheritance?noredirect=1#comment55840805_34042589) "If it's a problem it suggests the complexity is already present" – Jonathan Mee Dec 02 '15 at 13:24
  • @n.m. Excellent point, I made these notes without actually trying to implement the class, when I went to try it out, I realized you're exactly right only the third is an issue, and there is a work around for that. – Jonathan Mee Dec 02 '15 at 13:27
  • 2
    See also[this](http://stackoverflow.com/questions/15549722/double-inheritance-of-enable-shared-from-this). Unrestricted multiple inheritance from enable_shared_from_this doesn't work, but then using shared_ptr with unrestricted MI doesn't work either, so nothing of value is lost. – n. m. could be an AI Dec 02 '15 at 14:18
  • 3
    Re: "I've been warned a lot in my day about the dangers of inheriting from the standard library", that's far too broad. You should have been warned about deriving from things that aren't designed to be derived from. `enable_shared_from_this` **is** intended to be derived from, and generic fears about inheritance should not be a factor in deciding whether to use it in the way it's designed to be used. – Pete Becker Dec 02 '15 at 14:50
  • @n.m. Great comment, helpful link too, thanks, I've given you a random +1 – Jonathan Mee Dec 02 '15 at 15:21

1 Answers1

3

is there a way to use enable_shard_from_this as a has-a relationship instead of an is-a relationship?

No.

enable_shared_from_this is supposed to be used as a base class, so mindlessly applying a guideline meant for other situations doesn't work in this case.

Even if there was a good reason to want to do this (and there isn't) it wouldn't work. The magic that causes the enable_shared_from_this base to share ownership with the shared_ptr that owns the derived object works by checking for inheritance.

enable_shared_from_this doesn't model an IS-A relationship anyway, because it has no interface defined in terms of virtual functions. IS-A means a derived type that extends a base interface, but that isn't the case here. A Good IS-NOT-A enable_shared_from_this<Good>.

i.e. using inheritance does not always imply an IS-A relationship.

  1. enable_shared_from_this doesn't have a virtual destructor

A virtual destructor is irrelevant unless you plan to delete the object through a pointer to the enable_shared_from_this base class, which would be insane. There is no reason to ever pass around a Good as a pointer to the enable_shared_from_this<Good> base class, and still less reason to ever use delete on that base pointer (usually the type would be stored in a shared_ptr<Good> as soon as it's created, so you would never use delete at all).

enable_shared_from_this is a mixin type, not an abstract base. It provides the shared_from_this (and soon weak_from_this) member, that's all. You are not supposed to use it as an abstract base or an interface type, nor use the base type to access polymorphic behaviour of the derived type. The fact it has no virtual functions at all, not just no virtual destructor, should tell you that.

Furthermore, as n.m. commented above, the destructor is protected, so you can't delete it via the base class even if you tried (a protected destructor is the idiomatic way of preventing that type of misuse of mixin classes intended to be non-polymorphic base classes).

  1. The enable_shared_from_this destructor destroys *this, meaning it must always be the last destructor called

Huh? Not sure what you mean, but it isn't responsible for destroying anything except itself and its own data member.

  1. Inheritance from two classes that both inherit from enable_shared_from_this can become a bit of a sticking point

It should work OK (although you might not get the magic ownership sharing if there isn't a single unambiguous base class that is a specialization of enable_shared_from_this). The GCC standard library had a bug (now fixed) where it fails to compile, rather than just fail to share ownership, but that's not a problem with enable_shared_from_this.

Jonathan Wakely
  • 166,810
  • 27
  • 341
  • 521