2013-11-14 42 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另外:你實際上將什麼放在撤消列表中,並且當你製作時你如何期望它工作修改原始對象?從您的描述中,我得到了一個非常強烈的印象,即您需要深度複製(可能這些對象目前不可複製)。 –