114

I am currently trying to learn how to use smart pointers. However while doing some experiments I discovered the following situation for which I could not find a satifying solution:

Imagine you have an object of class A being parent of an object of class B (the child), but both should know each other:

class A;
class B;

class A
{
public:
    void addChild(std::shared_ptr<B> child)
    {
        children->push_back(child);

        // How to do pass the pointer correctly?
        // child->setParent(this);  // wrong
        //                  ^^^^
    }

private:        
    std::list<std::shared_ptr<B>> children;
};

class B
{
public:
    setParent(std::shared_ptr<A> parent)
    {
        this->parent = parent;
    };

private:
    std::shared_ptr<A> parent;
};

The question is how can an object of class A pass a std::shared_ptr of itself (this) to its child?

There are solutions for Boost shared pointers (Getting a boost::shared_ptr for this), but how to handle this using the std:: smart pointers?

Community
  • 1
  • 1
Icarus
  • 1,455
  • 2
  • 11
  • 12
  • 2
    As is with any other tool you have to use it when it's appropriate. Using smart pointers for what you are doing is *not* – YePhIcK Jul 29 '12 at 16:50
  • Similarly to boost. See [here](http://en.cppreference.com/w/cpp/memory/enable_shared_from_this). – juanchopanza Jul 29 '12 at 16:53
  • 1
    This is a problem at that level of abstraction. You don't even know that "this" points to memory on the heap. – Vaughn Cato Jul 29 '12 at 16:54
  • Well, the language doesn't, but _you_ do. As long as you keep track of what's where, you'll be fine. – Alex Sep 03 '14 at 17:04

2 Answers2

183

There is std::enable_shared_from_this just for this purpose. You inherit from it and you can call .shared_from_this() from inside the class. Also, you are creating circular dependencies here that can lead to resource leaks. That can be resolved with the use of std::weak_ptr. So your code might look like this (assuming children rely on existence of parent and not the other way around):

class A;
class B;

class A
    : public std::enable_shared_from_this<A>
{
public:
    void addChild(std::shared_ptr<B> child)
    {
        children.push_back(child);

        // like this
        child->setParent(shared_from_this());  // ok
        //               ^^^^^^^^^^^^^^^^^^
    }

private:     
    // note weak_ptr   
    std::list<std::weak_ptr<B>> children;
    //             ^^^^^^^^
};

class B
{
public:
    void setParent(std::shared_ptr<A> parent)
    {
        this->parent = parent;
    }

private:
    std::shared_ptr<A> parent;
};

Note however, that calling .shared_from_this() requires that this is owned by std::shared_ptr at the point of call. This means that you cannot create such object on stack anymore, and generally cannot call .shared_from_this() from within a constructor or destructor.

yuri kilochek
  • 11,212
  • 2
  • 25
  • 52
  • 2
    Thank you for your explanation and for pointing out my circular dependency problem. – Icarus Jul 29 '12 at 17:11
  • Try to construct a `shared_ptr` based on a default-constructed `shared_ptr` and whatever you want to point it at... – Deduplicator Oct 25 '15 at 17:13
  • 1
    @Deduplicator that's a, pardon my pun, rather pointless shared pointer. That constructor is intended to be used with pointers to members of the managed object or its bases. In any case what is your point (I'm sorry)? These non-owning `shared_ptr`s are are irrelevant to this question. `shared_from_this`'s preconditions clearly state that the object must be owned (not just pointed to) by some `shared_ptr` at the point of call. – yuri kilochek Oct 25 '15 at 22:16
  • @yurikilochek Could you please elaborate on why exactly it's impossible to call `.shared_from_this()` in constructor? Is it because the object is not yet created during constructor execution and non-determined value of `this` pointer? – kazarey Jul 05 '16 at 17:53
  • 1
    @kazarey Ownership by a `shared_ptr`is required at the point of call, but in a typical usage pattern, i.e. something like `shared_ptr p(new Foo());`, `shared_ptr` assumes ownership of the object only after it is fully constructed. It is possible to circumvent this by creating `shared_ptr` in constructor initialized with `this` and storing it somewhere non-local (e.g. in a reference argument) so it doesn't die when constructor completes. But this convoluted scenario is unlikely to be necessary. – yuri kilochek Jul 06 '16 at 00:07
  • 1
    given that the Parent normally should have the ownership of Children, isn't it confusing to declare a weak_ptr of children in the parent class, while the children hold the ownership(shared_ptr) of the parent? so who own the children now? – pengMiao Sep 07 '18 at 03:32
  • 1
    What's holding the tree together when the reference to the children are only weak pointers and only the root node is held directly? – Jimmy R.T. Mar 31 '20 at 14:34
  • @JimmyR.T. something else. Think of it as a set of stacks that share common prefix rather than a tree per se. – yuri kilochek Apr 01 '20 at 01:32
  • 1
    Gave a upcount on this response for answering the OPs question. Those who don't but suggest the "better way" are often mistaken. Also did an upcount because I needed this answer for a problem. An idiom I've used multiple times is putting "this" in a container to obtain a complete list of all class instances, i.e. to walk the container to process every instance. Please don't exert yourself explaining why this is problematic. I know the problems and how to avoid them. – rm1948 Oct 02 '20 at 04:05
8

You have several problems in you design, that seem to stem from you misunderstanding of smart pointers.

Smart pointers are used to declare ownership. You are breaking this by declaring that both the parents owns all children, but also that each child own it's parent. Both can't be true.

Also, you are returning a weak pointer in getChild(). By doing so, you are declaring that the caller shouldn't care about the ownership. Now this can be very limiting, but also by doing so, you must make sure that the child in question won't get destroyed while any weak pointers are still held, if you would use a smart pointer, it would get sorted out by itself.

And the final thing. Usually, when you are accepting new entities, you should usually accept raw pointers. Smart pointer can have their own meaning for swapping children between parents, but for general usage, you should accept raw pointers.

Šimon Tóth
  • 33,420
  • 18
  • 94
  • 135