Search code examples
c++interfaceshared-ptrsmart-pointers

Interface methods and returning smart pointer to this (enable_shared_from_this)


I have a class (Geometry) that implements an interface (LinkList). The code at the bottom of my example is obviously wrong, so I'm looking at implementing std::enable_shared_from_this. Can Geometry safely inherit from std::enable_shared_from_this<LinkList> and can I safely change the getLinkList() method to shared_from_this()? What happens if my Geometry class has multiple interfaces that it inherits, can I do this for all of the interfaces?

class Link
{
public:
    std::string getName() { return "the name"; }
};
class LinkList
{
public:
    virtual Link* getLink(int id) = 0;
    virtual int size() = 0;
};
class Geometry : LinkList
{
private:
    int state;
public:
    void doSomething() { state = 1; }
    virtual Link* getLink(int id) { return new Link(); }
    virtual int size() { return 1; }
    std::shared_ptr<LinkList> getLinkList() { return std::shared_ptr<LinkList>(this); }
};

void printList(std::shared_ptr<LinkList> linkList)
{
    for (int i = 0; i < linkList->size(); i++)
    {
        std::cout << linkList->getLink(i)->getName() << std::endl;
    }
}

void main()
{
    Geometry* geom = new Geometry();
    printList(geom->getLinkList());
    geom->doSomething(); // Error here
}

Solution

  • The code at the bottom of my example is obviously wrong

    The code there is not obviously wrong, the bug is in getLinkedList which gives ownership of this to a shared_ptr causing it to be deleted when the printList returns and the last shared_ptr object goes out of scope. So the last line of the example is rather non-obviously wrong, because the previous line subtly deleted the object.

    Can Geometry safely inherit from std::enable_shared_from_this<LinkList>

    Yes, it's just a base class. It's nearly always safe to inherit from it (that doesn't mean you can use it safely though!)

    and can I safely change the getLinkList() method to shared_from_this()?

    Only if the Geometry object is owned by a shared_ptr, and in your code it isn't:

    Geometry* geom = new Geometry();
    

    You need to store the pointer in a shared_ptr (preferably immediately when you create it) for the enable_shared_from_this base class to be usable, so this should be OK:

    auto geom = std::make_shared<Geometry>();
    printList(geom->shared_from_this());
    geom->doSomething();
    

    (Of course using shared_from_this() is pretty pointless there, because you already have the sahred_ptr anyway and could just do printList(geom)).

    What happens if my Geometry class has multiple interfaces that it inherits, can I do this for all of the interfaces?

    No, if the class has multiple enable_shared_from_this base classes then the shared_ptr constructors won't know which one to initialize. I think you'll get an error due to ambiguity, or if not the enable_shared_from_this base classes will all hold an empty weak_ptr making them useless.

    Update:

    You could make Geometry derive from enable_shared_from_this<Geometry> (not from enable_shared_from_this<LinkList> and not from every interface), and define:

    shared_ptr<LinkList> Geometry::getLinkList()
    {
      return shared_from_this();
    }
    

    The object returned by shared_from_this() will convert implicitly to shared_ptr<LinkList>. You still need this to be owned by a shared_ptr in the first place though.