9

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.

mskfisher
  • 3,291
  • 4
  • 35
  • 48
Omnifarious
  • 54,333
  • 19
  • 131
  • 194

8 Answers8

9

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.

svick
  • 236,525
  • 50
  • 385
  • 514
Fred Foo
  • 355,277
  • 75
  • 744
  • 836
  • 2
    This is what `enable_shared_from_this` already does. You've also got a stray * in your declaration -- you want a shared_ptr, not a pointer to a shared_ptr. :) – Billy ONeal Nov 15 '11 at 18:03
  • This is a good answer, but in my situation I would not have a 'dependents' argument. This function would loop through all the dependencies adding the newly created node (i.e. `np`) to the list of the dependents of each dependency. – Omnifarious Nov 15 '11 at 18:16
  • @larsmans: Thanks. I will likely accept your answer in a little while. But I wanted the update because otherwise it's not congruent with my desire to have this in the constructor. – Omnifarious Nov 15 '11 at 18:24
  • 1
    FWIW, this is known as the *named constructor idiom*. See http://www.parashift.com/c++-faq-lite/ctors.html#faq-10.8 – Michael Price Nov 16 '11 at 14:59
5

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.

Michael Krelin - hacker
  • 138,757
  • 24
  • 193
  • 173
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.

Christian Rau
  • 45,360
  • 10
  • 108
  • 185
Basile Starynkevitch
  • 223,805
  • 18
  • 296
  • 547
  • I really don't see how this answers the question. – Billy ONeal Nov 15 '11 at 18:00
  • Yours is the most out-of-the-box answer, and interestingly enough, I already thought of it and discarded the idea. But yeah... you're right. Unfortunately this is intended as part of a re-usable library for others, and requiring GC is a non-starter for many C++ people (including, oftentimes, me). – Omnifarious Nov 15 '11 at 18:00
  • @BillyONeal: Basile is suggesting I drop `shared_ptr` reference counts and go with a full-blown garbage collector instead. – Omnifarious Nov 15 '11 at 18:01
  • @BillyONeal It may not answer the question, but then it is not irrelevant to the subject either. Hope you didn't downvote it ;-) – Michael Krelin - hacker Nov 15 '11 at 18:09
  • @MichaelKrelin-hacker: I removed my downvote after Omnifarious explained it a bit. I still think it's a bad answer but not a downvote worthy answer. – Billy ONeal Nov 15 '11 at 18:11
  • 1
    @BillyONeal I haven't made up my mind whether it's good or bad, I just think it's legitimate and I'm glad we're of the same opinion. – Michael Krelin - hacker Nov 15 '11 at 18:13
1

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.

Alan Stokes
  • 18,815
  • 3
  • 45
  • 64
1

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.

Billy ONeal
  • 104,103
  • 58
  • 317
  • 552
  • This would be OK if it weren't for the fact that an object can have multiple dependents. This is a DAG, not a tree. The branches can join back together, there just can't be any cycles. – Omnifarious Nov 15 '11 at 18:14
  • @Omnifarious: Ok. Then Factory Method is your only option. It's the only way to enforce that you're actually always constructing objects using `shared_ptr`s instead of in static or automatic storage, for example. (On the plus side, you can take advantage of the kinds of optimizations by make_shared in the factory). – Billy ONeal Nov 15 '11 at 20:57
1

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).

Jerry Coffin
  • 476,176
  • 80
  • 629
  • 1,111
  • This is an interesting answer. I'm not sure if I could alter my design this way. I'm trying to build a dependency based system for handling a future/promise-like arrangement for multi-threading. Nodes are pretty important entities. OTOH, there will be a manager for all the nodes that are part of a given thread. Hmmm... – Omnifarious Nov 15 '11 at 19:14
0

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.

dicroce
  • 45,396
  • 28
  • 101
  • 140
  • _chuckle_ I ended up creating constructor functions. Though, that has a different interesting issue: http://stackoverflow.com/questions/8147027/how-do-i-call-stdmake-shared-on-a-class-with-only-protected-or-private-const I will say that this does have the advantage of preventing people from creating these things as stack or embedded objects and then creating `shared_ptr`s to them. If people did that it would break a whole ton of stuff. I suspect that might be the case for you too. – Omnifarious Apr 24 '12 at 03:06
-1

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

Yochai Timmer
  • 48,127
  • 24
  • 147
  • 185
  • I don't think you can call shared_from_this unless there is already an owning `shared_ptr`; in the constructor there isn't.(20.7.2.4/7) – Alan Stokes Nov 15 '11 at 18:01
  • 1
    +1 for enable_shared_from_this -- it's basically implemented as a `weak_ptr` member exactly like the OP wants anyway. – Billy ONeal Nov 15 '11 at 18:01
  • @AlanStokes: You can't. However looking at the OP's case he seems to just want a weak pointer to the object stored in the object itself, which is exactly what enable_shared_from_this does. – Billy ONeal Nov 15 '11 at 18:02
  • -1: (Though an 'A' for effort!) It won't work. Calling `shared_from_this` when there isn't already a shared_ptr pointing at the object isn't legal. – Omnifarious Nov 15 '11 at 18:03
  • @Omnifarious: I'm confused. How is that a problem? Isn't your problem that you need to be able to reference the object itself as a weak_ptr? – Billy ONeal Nov 15 '11 at 18:05
  • @Billy True. It depends if the `weak_ptr` needs to be valid in the constructor (in which case I don't think there is a solution). – Alan Stokes Nov 15 '11 at 18:07
  • @Alan: That doesn't make any sense. What happens if the object is constructed e.g. on the stack? There's no way for the object to tell if it's constructed using a shared_ptr in the constructor. If that's what's going on the OP would have to fail over to the factory method construction as in larsmans' answer. – Billy ONeal Nov 15 '11 at 18:09
  • @BillyONeal: The weak_ptr does indeed have to be valid in the constructor. – Omnifarious Nov 15 '11 at 18:12
  • @Omnifarious: Ah, then C++ gives you no method to enforce that. You'll be stuck using factory method. (E.g. if someone constructed your object using automatic storage what would you expect the weak_ptr to be?) – Billy ONeal Nov 15 '11 at 18:13
  • @Omnifarious: The example wouldn't work for a different reason. You don't have an anchor. All the objects in the constructor's block get destructed in the end of the block. At least one shared pointer needs to hold the object. – Yochai Timmer Nov 15 '11 at 18:17
  • @YochaiTimmer: Unless you have a `shared_ptr` member variable, it's impossible to have an anchor in the constructor. And if you have a `shared_ptr` member variable, you've created a reference cycle that needs to be cleaned up. – Omnifarious Nov 15 '11 at 18:28