2014-10-06 95 views
4

在下面的代碼已經被證明是不安全的,因爲如果構造函數拋出,析構函數不會被調用,泄漏資源的演講:使用委託構造,防止泄漏

class TwoResources { 
    TwoResources(int x, int y) 
     : m_a(nullptr), m_b(nullptr) { 
     m_a = new A(x); m_b = new B(y); 
    } 
    ~TwoResources() { 
     delete m_b; delete m_a; 
    } 
    A * m_a; B * m_b; 
}; 

一個建議的解決方案是使用一個委派構造函數,如下所示:

class TwoResources { 
    TwoResources() : m_a(nullptr), m_b(nullptr) { } 
    TwoResources(int x, int y) : TwoResources() { 
     m_a = new A(x); m_b = new B(y); 
    } 
    ~TwoResources() { 
     delete m_b; delete m_a; 
    } 
    A * m_a; B * m_b; 
}; 

這是安全的,因爲的:

C++ 11 15.2 [except.ctor]/2: 「如果對象 的非委託構造函數已完成執行,並且該對象的委託構造函數存在異常,則該對象的析構函數將被調用 。

然而,在同一個幻燈片,它說:

僅僅因爲你可以利用這條規則並不意味着你應該!

如果此代碼保證是安全的,那麼它有什麼潛在的問題呢?

+0

這可能是壞主意,使用'新/ delete'手動在c + + 11代碼。你應該使用智能指針來避免這些問題。 – ForEveR 2014-10-06 11:02:34

+0

這是STL 2013年GoingNative的演講,對嗎? – 2014-10-06 11:07:50

+0

我認爲類名'TwoResources'暗示更可行的解決方案;每個類(或對象)一個資源。如果兩種資源的管理不同,則將每個資源包裝在自己的RAII類中,並使其成爲更高級別的成員。 – Niall 2014-10-06 11:49:50

回答

6

僅僅因爲某些東西是安全的並不意味着這樣做是一個好主意。例如,使用委託構造函數調用對於異常安全性至關重要,但對於不熟悉語言複雜性的代碼的臨時讀者來說,這遠遠不夠清楚。一個月後,其他人看着你的代碼可能會想「如果你再次將它設置在構造函數體中,你爲什麼要將它設置爲null?」並將其刪除。哎喲。

此外,當您手動管理生命週期時,您需要編寫自己的複製/移動構造函數/賦值運算符。想念一個而且破壞性的結果。如果您使用unique_ptr來管理生存期,那麼編譯器生成的移動構造函數/賦值運算符和析構函數會做正確的事情,如果您嘗試複製而不自己實現複製構造函數,它會投訴。

1

這當然不是很清楚。有人可能通過在一個構造函數中添加新變量而無需理解第二個變量的用處來獲得並修改代碼。 你應該考慮使用std::unique_ptr

class TwoResources 
{ 
    TwoResources(int x, int y) : m_a(new A(x)), m_b(new B(y)) 
    { 
    } 
    std::unique_ptr<A> m_a; 
    std::unique_ptr<B> m_b; 
}; 

同樣的,看看到std::make_unique這比使用新的手動安全和delete操作符:

class TwoResources 
{ 
    TwoResources(int x, int y):m_a(std::make_unique<A>(x)), m_b(std::make_unique<B>(B(y)) 
    { 
    } 
    std::unique_ptr<A> m_a; 
    std::unique_ptr<B> m_b; 
};