2013-11-14 43 views
4

我在我的程序中遇到了一个设计问题。 我必须管理作为根ChainDescriptor一部分的Nodes对象。C++:用共享和弱ptr替换原始指针

基本上它看起来如下:

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; 
}; 

的ChainDescriptor类拥有的所有节点,并负责删除它们。 但是这些类现在需要用在另一个程序中,一个具有撤销/重做功能的GUI,以及“所有权”的问题。 修改深度现有的代码之前,我考虑了不同的解决方案:

  • 使用weak_ptr和各自list<weak_ptr<...> >

在上面的例子中使用shared_ptr和各自list<shared_ptr<...> >

  • ,我不真的知道在哪里使用shared_ptrweak_ptr正确。

    有什么建议吗?

  • +0

    [boost :: ptr_list']怎么样(http://www.boost.org/doc/libs /1_55_0/libs/ptr_container/doc/ptr_list.html)? –

    回答

    3

    您可以使用shared_ptr代替m_childsweak_ptr代替m_parent

    但是,将原始指针保留给父代Node可能仍然是合理的,并且根本不使用任何弱指针。背后的安全机制是非空父节点总是存在的不变量。

    另一种选择是仅在ChainDescriptor中使用shared_ptr并保留所有原始指针Node。这种方法避免了弱指针,并拥有干净的所有权策略(父节点拥有自己的子女)。

    弱指针会帮助你自动管理内存,但是这背后是模糊的所有权逻辑和性能处罚。

    +2

    +1提到原始指针的选项。我认为他们是表达m_parent所有权状况的正确工具。 – risingDarkness

    +0

    @谢谢,我同意。 我开始写这个方向的原型。 ChainDescriptor将包含shared_ptr列表。对于Node类,我开始编写weak_ptr 列表,但是在列表中搜索节点有点棘手,因为在weak_ptr中没有定义==运算符。 – Zyend

    +0

    你是否很少通过''weak_ptr''访问父对象?如果是这样,''weak_ptr''是你最好的朋友。如果您需要始终访问父项,请在内部循环的某处查看原始指针。他们并不坏。 –

    2

    shared_ptr拥有智能指针,weak_ptr引用智能指针。

    所以在你的情况我觉得ChainDescriptor应该使用shared_ptr(它拥有的节点)和Node应该使用m_childsweak_ptrm_parent(只引用它)和shared_ptr(它拥有它们)。

    +0

    你可以解释'weak_ptr'在父级的原始指针上的优点吗? –

    +1

    'weak_ptr'的使用是安全的。原始指针的有效性必须通过不变量来保证(如Sergey的回答中所述)。 'weak_ptr'的主要问题是它的性能影响 - 如果你需要速度,没有什么能够与原始指针竞争。 – Johny

    +0

    'weak_ptr'的使用很复杂,我不知道它在哪里确实增加了安全性。这不仅仅是一个速度问题,它也是一个易用性的问题。如果'weak_ptr'保护程序员可能实际做出的某种错误,那将是一回事。但是在这种情况下,他的代码只支持一个父代,并且删除该父代会删除该子代,因此,一旦父代被删除,可能无法访问指向父代的指针。 –

    1

    通常的实现是为每个节点强有力地引用它的子节点(即保持它们活着),并且每个子节点都有一个弱引用返回给父节点。

    原因是为了避免循环引用。如果只使用了强引用,那么你会遇到父引用计数从不下降到零的情况(因为孩子有引用),并且子引用计数永远不会降为零(因为父引用)。

    我认为你的ChainDescriptor类可以在这里使用强引用。

    +0

    通常的实现会使用指向父指针的原始指针。这里没有必要使用弱指针,因为所有权语义可以保证孩子无法活过父母。取决于结构:如果它是一个DAG,则必须共享子指针,否则,'unique_ptr'就可以完成这项工作,而复杂性和开销也会小得多。 –

    +0

    真的......我的想法是,如果在某个时候需要一个'Node :: getParent()'函数,那么你会希望它返回一个完整的'shared_ptr',在这种情况下,它会更容易“升级”持有的'weak_ptr'而不是使用'enable_shared_from_this'。 –

    +0

    @Tristan,我同意。 ChainDescriptor包含整个节点列表。但是,如果ChainDescriptor删除了一个节点(即chainDesc.removeNode(“blabla”)),它将从主列表中删除它,但也从所有其他具有“blabla”作为子节点的节点中删除它。 – Zyend

    1

    试图用某种智能替换原始指针 指针将一般不起作用。智能指针具有与弱指针不同的语义 ,并且通常需要在更高的 级别考虑这些特殊语义。这里最干净的解决方案是在ChainDescriptor中添加对复制 的支持,实现深层复制。 (我假设 在这里你可以克隆Node,并且所有的Node都是 总是拥有ChainDescriptor。)另外,为了撤消,你可能需要一个深拷贝;您不希望修改 活动实例来修改为撤消保存的数据。

    话虽如此,你的节点似乎被用来形成一棵树。在 这种情况下,std::shared_ptr将工作,只要1)所有Node 总是“拥有”,通过无论是ChainDescriptor或父母 Node,和2)结构真的是一片森林,或至少 DAG的集合(当然,在任何已保存的实例中,您都不会更改 )。如果结构可能会发生 循环,则不能在此 级别使用shared_ptr。您可能能够将节点列表和树结构抽象为单独的实现类,并且 ChainDescriptor保留shared_ptr

    (FWIW:我用了一个参考 计数指针节点解析树我多年前写的,并且不同的实例 可以分享子树,但我设计的,它从 开始使用引用计数指针。由于构建树的方式,我保证不会有 个周期。)

    +0

    是的,一个节点总是由链或父节点开启。但是,如果从链中删除了一个节点(即chain.removeNode(“blabla”)),那么该机制也应该从它拥有它的任何节点的子列表中删除它。 – Zyend

    +0

    @Zyend我不知道有哪个智能指针你的意思是你必须导航到父母,并从中删除节点? –

    +0

    @Zyend另外:你实际上将什么放在撤消列表中,并且当你制作时你如何期望它工作修改原始对象?从您的描述中,我得到了一个非常强烈的印象,即您需要深度复制(可能这些对象目前不可复制)。 –