C++: Replace raw pointers with shared and weak ptr

2020-03-25 05:32发布

I'm facing a design issue in my program. I have to manage Nodes object which are part of a root ChainDescriptor.

Basically it looks like the following:

class ChainDescriptor
{
public:
    ~ChainDescriptor()
    {
        //delete the nodes in nodes...
    }

    void addNode(Node *);
    Node * getNode();

    const std::list<Node *>& getNodes() const;

    std::list<Node *> m_nodes;

};

class Node
{
public:
    Node(Node *parent);

    void addChild(Node *node);
    Node * getChild(const std::string& nodeName);

private:
    Node * m_parent;
    std::list<Node*> m_childs;
};

The ChainDescriptor class owns all the nodes and is responsible of deleting them. But these classes need now to be used in another program, a GUI with undo/redo capabilities, with the problematic of the "ownership". Before modifying the existing code in depth, I'm considering the different solutions:

  • using shared_ptr and respective list<shared_ptr<...> >
  • using weak_ptr and respective list<weak_ptr<...> >

In the example above, I don't really know where to use shared_ptr and weak_ptr properly.

Any suggestion?

4条回答
劫难
2楼-- · 2020-03-25 05:56

You can use shared_ptr for m_childs and weak_ptr for m_parent.

However, it might be still reasonable to retain the raw pointer to the parent Node and don't use any weak pointers at all. The safeguarding mechanism behind this is the invariant that non-null parent always exists.

Another option is using shared_ptr in ChainDescriptor only and retaining all raw pointers in Node. This approach avoids weak pointers and has a clean ownership policy (parent nodes own their children).

Weak pointers will help you to manage the memory automatically, but the backside of this are fuzzy ownership logic and performance penalties.

查看更多
疯言疯语
3楼-- · 2020-03-25 06:02

shared_ptr is owning smart pointer and weak_ptr is referencing smart pointer.

So in your situation I think the ChainDescriptor should use shared_ptr (it owns the nodes) and Node should use weak_ptr for m_parent (it only references it) and shared_ptr for m_childs (it owns them).

查看更多
别忘想泡老子
4楼-- · 2020-03-25 06:02

Trying to just replace raw pointers with some sort of smart pointer will in general not work. Smart pointers have different semantics than weak pointers, and usually, these special semantics need to be taken into account at a higher level. The "cleanest" solution here is to add support for copy in ChainDescriptor, implementing a deep copy. (I'm supposing here that you can clone Node, and that all of the Node are always owned by a ChainDescriptor.) Also, for undo, you may need a deep copy anyway; you don't want modifications in the active instance to modify the data saved for an undo.

Having said that, your nodes seem to be used to form a tree. In this case, std::shared_ptr will work, as long as 1) all Node are always "owned" by either a ChainDescriptor or a parent Node, and 2) the structure really is a forest, or at least a collection of DAG (and, of course, you aren't making changes in any of the saved instances). If the structure is such that cycles may occur, then you cannot use shared_ptr at this level. You might be able to abstract the list of nodes and the trees into a separate implementation class, and have ChainDescriptor keep a shared_ptr to this.

(FWIW: I used a reference counted pointer for the nodes in a parse tree I wrote many years ago, and different instances could share sub-trees. But I designed it from the start to use reference counted pointers. And because of how the tree was constructed, I was guaranteed that there could be no cycles.)

查看更多
放我归山
5楼-- · 2020-03-25 06:10

The usual implementation would be for each node to have strong reference to its child (i.e. keeps them alive), and each child to have a weak reference back to the parent.

The reason for this is to avoid circular references. If only strong references were used, then you'd have a situation where the parent refcount never drops to zero (because the child has a reference), and the child refcount never drops to zero (because the parent has a reference).

I think your ChainDescriptor class is okay to use strong references here though.

查看更多
登录 后发表回答