Smart pointers + cycles + “->”

2019-03-19 04:38发布

问题:

Sometimes I'm really sure that I want to have circular dependence of pointers, and every object on cycle should be able to use his pointer (so it can't be weak_ptr).

My question is: Does this mean that I have bad design?

What if I want to implement graph? Can I use smart pointers? In graphs there are cycles, but with weak_ptr I can't use "->". What can I do?

I read some articles, reference and topics on StackOverflow, but it looks like I still don't get smart pointers. Really, why doesn't exists some variant of weak_ptr with "->"?

回答1:

Approach this from the conceptual side, not the implementation one. Smart pointers represent ownership. And existence of smart pointers does not invalidate the role of raw pointers as non-owning observers.

Does each object have a single, clearly defined owner (e.g. a graph owns all of its vertices and edges)? If so, use std::unique_ptr to hold the vertices and edges in the graph, and use raw pointers inside vertices and edges to refer to each other.

Is shared ownership applicable (e.g. a vertex only exists as long as at least one edge is connected to it)? If so, use std::shared_ptr to represent that ownership, again with raw pointers for non-owning observers. If you need mutual ownership (i.e. ownership cycles) where "a vertex only exists as long as an edge refers to it, and an edge only exists as long as a vertex refers to it," then 1. double-check that such design is correct and maintainable, and 2. if so, use a std::weak_ptr somewhere in the cycle to break the ownership loop. You can always lock() a weak_ptr to obtain a shared_ptr.

For your particular graph scenario, I believe "everything's owned by the graph" would be the most logical ownership scheme; but that depends on the idiosyncracies of your task.



回答2:

You can use weak_ptr somewhere in the cycle; you just need to promote the weak_ptrs to shared_ptrs before you can dereference them. You can do this by calling weak_ptr::lock() or simply by passing a weak_ptr to shared_ptr's constructor (but beware; this will throw a bad_weak_ptr exception if the object the weak_ptr points to has been destroyed.

If you really can't do this (for example, if all objects involved in the cycle are of the same type, which is probably the case in your graph example), another option is to put a release function somewhere in the chain that causes the object in question to set all its shared_ptrs to null.



回答3:

Does this mean that I have bad design?

Yes, but it is a starting point.

Let's consider some of the smart pointers availabe to use.

unique_ptr - a single owner exists that is responsible for disposing of the object.

shared_ptr - many (or potentially many) owners exist and the last one must dispose of the object

weak_ptr - many owners may exist but this is not one of them, the weak pointer may out live the object pointed to, if the object pointed to is disposed of the weak pointer will be null (that is the lock method will return a null shared_ptr)

observer_ptr(n3840)- Not yet part of the standard so C-style pointers (T*) can be used instead if needed. These work very much like a weak_ptr, but it is the programmer’s responsibility to make sure that all observers are not dereferenced after the object pointed to is disposed of.

A solution is to split the design into an object that will own all the pieces and the pieces (the cycle nodes). The owning object can use shared_ptr or unique_ptr to automatically manage the life time of the nodes. The nodes themselves can refer to each other with weak_ptr, observer_ptr, or a Reference (Node&)