2010-05-19 53 views
1

我在刷新了C++知識後,多年沒有使用它。在編寫一些代碼來實現一些實踐數據結構時,我想確保我的代碼是異常安全的。所以我試圖用我認爲合適的方式使用std::auto_ptr。有些簡化,這是我有:auto_ptr將所有權轉移到容器的習慣性使用

class Tree 
{ 
public: 
    ~Tree() { /* delete all Node*s in the tree */ } 
    void insert(const string& to_insert); 
    ... 

private: 
    struct Node { 
     ... 
     vector<Node*> m_children; 
    }; 

    Node* m_root; 
}; 

template<T> 
void push_back(vector<T*>& v, auto_ptr<T> x) 
{ 
    v.push_back(x.get()); 
    x.release(); 
} 

void Tree::insert(const string& to_insert) 
{ 
    Node* n = ...; // find where to insert the new node 
    ... 
    push_back(n->m_children, auto_ptr<Node>(new Node(to_insert)); 
    ... 
} 

所以我包裹,將放指針放入容器內,vector::push_back功能,並依託由價值auto_ptr參數 確保Node*是如果vector調整大小失敗,則將其刪除。

這是一種慣用的使用auto_ptr在我的 Tree::insert保存一些樣板?你可以提出任何改進建議?否則,我得有 類似:雜波的

Node* n = ...; // find where to insert the new node 
auto_ptr<Node> new_node(new Node(to_insert)); 
n->m_children.push_back(new_node.get()); 
new_node.release(); 

哪種了什麼將是一個單一的代碼行,如果我不 擔心異常安全和內存泄漏。 (其實我想知道是否可以發佈我的整個代碼示例(約300行),並要求人們批評它的習慣C++用法,但我不確定這種問題是否適合在計算器上。)

回答

2

這不是地道寫自己的容器:它是比較特殊的,並只爲學習如何編寫集裝箱有用的大部分。無論如何,在標準容器中使用std::autp_ptr肯定不是慣用的。事實上,這是錯誤的,因爲std::auto_ptr的副本並不相同:在任何給定時間只有一個auto_ptr擁有指針。

而對於習慣使用std::auto_ptr,你應該總是命名建設的auto_ptr

int wtv() { /* ... */ } 
void trp(std::auto_ptr<int> p, int i) { /* ... */ } 

void safe() { 
    std::auto_ptr<int> p(new int(12)); 
    trp(p, wtv()); 
} 

void danger() { 
    trp(std::auto_ptr<int>(new int(12)), wtv()); 
} 

因爲C++標準允許參數以任意順序評估,調用danger()是不安全的。在調用trp(),danger()時,編譯器可以分配整數,然後創建auto_ptr,最後調用wtv()。或者,編譯器可能會分配一個新整數,請致電wtv(),最後再創建auto_ptr。如果wtv()引發異常,則danger()可能泄漏或不泄漏。

safe()的情況,但是,因爲auto_ptr構造先驗,RAII保證它會正常wtv()是否拋出異常清理。

+0

重新編寫自己的容器,當然,我通常會尋找現有的實現。 (在這種情況下,我寫的真正的數據結構就是一個帕特里夏。)但是如前所述,寫這本書比實踐更適合實踐。我知道在STL容器中使用auto_ptrs是不可能的。 有關論證評價順序的觀點很好!在我的情況下,我應該沒問題,因爲另一個參數只是一個成員查找,但我想這是一致的,總是命名我的auto_ptrs以避免這個潛在的問題。 – heycam 2010-05-19 01:43:12

0

我喜歡聲明指針所有權的想法。這是C++ 0x,std::unique_ptr s中的重要特性之一。然而std::auto_ptr是如此難以理解和致命甚至有點誤會我建議完全避免它。

2

是的。

例如,請參閱Boost.Pointer容器庫的接口。各種指針容器都具有插入功能,採用auto_ptr,它在語義上保證它們擁有所有權(它們也具有原始指針版本,但是hey:p)。

然而,還有其他方法可以實現您在異常安全方面所做的工作,因爲這只是內部的一部分。要理解它,你需要了解什麼可能會出錯(即拋出),然後重新排列你的指令,以便在發生副作用之前完成可能拋出的操作。

例如,從您的帖子服用:

auto_ptr<Node> new_node(new Node(to_insert)); // 1 
n->m_children.push_back(new_node.get());  // 2 
new_node.release();       // 3 

讓我們來看看每一行,

  1. 構造函數可能會拋出(例如,如果該類型的CopyConstructor拋出),然而在這種情況您保證new將爲您執行清理操作
  2. 如果內存耗盡,致電push_back可能會拋出std::bad_alloc。這是唯一可能的錯誤就像複製指針是無拋出操作
  3. 這並不能保證扔

如果你仔細觀察,你會此話,你就不用擔心,如果你能以某種方式1.有前執行2事實上,這是可能的:

n->m_children.reserve(n->m_children.size() + 1); 
n->m_children.push_back(new Node(to_insert)); 

reserve的調用可能會拋出(bad_alloc),但是否能正常完成你再保證不會發生再分配,直到size等於capacity你試試另一個插入。

new的通話可能會掉落,如果構造函數拋出,在這種情況下new將進行清理,如果它完成你留下了一個指針在vector立即插入... ...這是不能保證扔因爲上面的線。

因此,auto_ptr的使用可能會在此處被替換。儘管如此,這是一個好主意,不過正如您已經注意到的,您應該避免在函數評估中執行RAII初始化。

+0

感謝您使用'reserve'來避免'auto_ptr'的提示。在我切換到使用'auto_ptr'之前,我確實已經在我的代碼中使用了,因爲我認爲它會更清晰。 – heycam 2010-05-20 23:09:03