Omnifarious
Omnifarious

Reputation: 56038

How do I hand out weak_ptrs to this in my constructor?

I'm creating a class that will be part of a DAG. The constructor will take pointers to other instances and use them to initialize a dependency list.
After the dependency list is initialized, it can only ever be shortened - the instance can never be added as a dependency of itself or any of its children.

::std::shared_ptr is a natural for handling this. Reference counts were made for handling DAGs.

Unfortunately, the dependencies need to know their dependents - when a dependency is updated, it needs to tell all of its dependents.
This creates a trivial cycle that can be broken with ::std::weak_ptr. The dependencies can just forget about dependents that go away.

But I cannot find a way for a dependent to create a ::std::weak_ptr to itself while it's being constructed.

This does not work:

object::object(shared_ptr<object> dependency)
{
     weak_ptr<object> me = shared_from_this();
     dependency->add_dependent(me);
     dependencies_.push_back(dependency);
}

That code results in the destructor being called before the constructor exits.

Is there a good way to handle this problem? I'm perfectly happy with a C++11-only solution.

Upvotes: 9

Views: 3747

Answers (8)

Fred Foo
Fred Foo

Reputation: 363547

Instead of a constructor, use a function to build the nodes of your graph.

std::shared_ptr<Node> mk_node(std::vector<std::shared_ptr<Node>> const &dependencies)
{
    std::shared_ptr<Node> np(new Node(dependencies));
    for (size_t i=0; i<dependencies.size(); i++)
        dependencies[i].add_dependent(np);       // makes a weak_ptr copy of np
    return np;
}

If you make this a static member function or a friend of your Node class, you can make the actual constructor private.

Upvotes: 9

dicroce
dicroce

Reputation: 46770

This has been driving me nuts as well.

I considered adopting the policy of using pointers to break cycles... But I'm really not found of this because I really like how clear the intent of the weak_ptr is when you see it in your code (you know it's there to break cycles).

Right now I'm leaning toward writing my own weak_ptr class.

Upvotes: 0

Jerry Coffin
Jerry Coffin

Reputation: 490108

It sounds to me like you're trying to conflate to somewhat different items: a single object (a node in the DAG), and managing a collection of those objects.

class DAG { 

    class node {
        std::vector<std::weak_ptr<node> > dependents;
    public:
        node(std::vector<weak_ptr<node> > d) : dependents(d) {}
    };

    weak_ptr<node> root;
};

Now, it may be true that DAG will only ever hold a weak_ptr<node> rather than dealing with an instance of a node directly. To the node itself, however, this is more or less irrelevant. It needs to maintain whatever key/data/etc., it contains, along with its own list of dependents.

At the same time, by nesting it inside of DAG (especially if we make the class definition of node private to DAG), we can minimize access to node, so very little other code has to be concerned with anything about a node. Depending on the situation, you might also want to do things like deleting some (most?) of the functions in node that the compiler will generate by default (e.g., default ctor, copy ctor, assignment operator).

Upvotes: 1

Billy ONeal
Billy ONeal

Reputation: 106530

Unfortunately, the dependencies need to know their dependents. This is because when a dependency is updated, it needs to tell all of its dependents. And there is a trivial cycle. Fortunately, this cycle can be broken with ::std::weak_ptr. The dependencies can just forget about dependents that go away.

It sounds like a dependency can't reference a dependent unless the dependent exists. (E.g. if the dependent is destroyed, the dependency is destroyed too -- that's what a DAG is after all)

If that's the case, you can just hand out plain pointers (in this case, this). You're never going to need to check inside the dependency if the dependent is alive, because if the dependent died then the dependency should have also died.

Just because the object is owned by a shared_ptr doesn't mean that all pointers to it themselves must be shared_ptrs or weak_ptrs - just that you have to define clear semantics as to when the pointers become invalidated.

Upvotes: 1

Yochai Timmer
Yochai Timmer

Reputation: 49231

Maybe this will help:

inherit from enable_shared_from_this which basically holds a weak_ptr. This will allow you to use this->shared_from_this();

shared_ptr's know to look if the class inherits from the class and use the classes weak_ptr when pointing to the object (prevents 2 shared pointers from counting references differently)

More about it: cppreference

Upvotes: -1

I'm understanding your question as conceptually related to garbage collection issues.

GC is a non-modular feature: it deals with some global property of the program (more precisely, being a live data is a global, non-modular, property inside a program - there are situations where you cannot deal with that in a modular & compositional way.). AFAIK, STL or C++ standard libraries does not help much for global program features.

A possible answer might be to use (or implement yourself) a garbage collector; Boehm's GC could be useful to you, if you are able to use it in your entire program.

And you could also use GC algorithms (even copying generational ones) to deal with your issue.

Upvotes: 1

Alan Stokes
Alan Stokes

Reputation: 18964

You can't.

The best I've come up with is to make the constructor private and have a public factory function that returns a shared_ptr to a new object; the factory function can then call a private method on the object post-construction that initialises the weak_ptr.

Upvotes: 1

Michael Krelin - hacker
Michael Krelin - hacker

Reputation: 143071

Basically, you can't. You need a shared_ptr or weak_ptr to make a weak_ptr and obviously self can only be aware of of its own shared_ptr only in form of weak_ptr (otherwise there's no point in counting references). And, of course, there could be no self-shared_ptr when the object isn't yet constructed.

Upvotes: 5

Related Questions