2015-11-06 51 views
3

在一個項目上工作時,當傳入的對象保證超時(就內存使用期限而言)收件人對象時,通過其構造函數將對象傳遞給另一個對象時,我遇到了一個有趣的問題。請記住,我仍然在學習C++ 11/C++ 14的入門知識,所以我在尋找有建設性的討論,這將有助於我用C++ 11/C++來理解內存管理和生命週期。 C++ 14風格的語義。如果一個對象被保證超出其包含的對象時應該如何存儲?

對於這個問題的設置如下:

class TopLevelClass { 
public: 
    void someMethod (int someValue) { 
     // Do some work 
    } 

    std::unique_ptr<Context> getContext() { 
     return std::make_unique<Context>(this); 
    } 
}; 

class Context { 
public: 
    Context (TopLevelClass* tlc) : _tlc(tlc) {} 

    void call (int value) { 
     // Perform some work and then call the top level class... 
     _tlc->someMethod(value); 
    } 

protected: 
    TopLevelClass* _tlc; 
}; 

雖然這種設置有效的替代方案將是在TopLevelClass作爲參數傳遞到Context類的call方法,這是不可能的在我正在說明的場景:訪問Context對象的客戶端代碼可能無法訪問TopLevelClass對象。

雖然上面說明的代碼功能正確,但我覺得存在代碼異味。也就是說,將TopLevelClass對象的句柄存儲爲原始指針並不能表明Context類不負責管理此指針的生存期(因爲在這種情況下,TopLevelClass保證會超過任何Context對象)。此外,使用C++ 11,我對使用原始指針而不是智能指針猶豫不決(按照Scott Meyer在Effective Modern C++中的建議)。

我探索的另一種方法是使用共享指針將句柄傳遞給TopLevelClass,並將該句柄作爲共享指針存儲在Context類中。這要求TopLevelClass以下面的方式從std::enabled_shared_from_this繼承:

class TopLevelClass : public std::enable_shared_from_this<TopLevelClass> { 
public: 
    // Same "someMethod(int)" as before... 

    std::unique_ptr<Context> getContext() { 
     return std::make_unique<Context>(shared_from_this()); 
    } 
}; 

class Context { 
public: 
    Context (std::shared_ptr<TopLevelClass> tlc) : _tlc(tlc) {} 

    // Same "call(int)" as before... 

protected: 
    std::shared_ptr<TopLevelClass> _tlc; 
}; 

這樣做的缺點的方法是,除非存在對TopLevelClass先驗一個std::shared_ptr,則std::bad_weak_ptr會拋出異常(有關詳細信息,見this post)。因爲在我的情況下,在代碼中沒有創建std::shared_ptr<TopLevelClass>,所以我不能使用std::enable_shared_from_this<T>方法:根據我的項目要求,我僅限於使用static原始指針返回TopLevelClass的單個實例,如下所示

static TopLevelClass* getTopLevelClass() { 
    return new TopLevelClass(); 
} 

是否有存在傳達的事實,Context不負責管理其句柄TopLevelClass情況下,因爲TopLevelClass將保證活得比任何Context對象的方法?我也對改變設計的建議持開放態度,只要設計更改不會過度複雜化上述設計的簡單性(即創建許多不同的類以簡單地傳遞進入構造函數Context的單指針)。

謝謝你的幫助。

+3

不是傳遞一個指針,而是傳遞並存儲一個引用。 – clcto

+0

有什麼理由不能包裝getTopLevelClass函數嗎?如果有,請參考。 –

+3

'getTopLevelClass()'是邪惡的。從簽名你可能會期望它返回一個單身人士,但實際上它返回一個擁有的原始指針到一個新的對象,每次你打電話時,調用者必須記得刪除! –

回答

1

一種選擇是使用類型定義來傳達非所有權:

#include <memory> 

template<typename T> 
using borrowed_ptr = T *; 

class TopLevelClass; 

class Context { 
public: 
    Context(borrowed_ptr<TopLevelClass> tlc) 
    : _tlc(std::move(tlc)) 
    { } 

private: 
    borrowed_ptr<TopLevelClass> _tlc; 
}; 

class TopLevelClass { 
public: 
    std::unique_ptr<Context> getContext() { 
    return std::make_unique<Context>(this); 
    } 
}; 

了清晰表達的意圖,雖然_tlc還是直接轉換爲原始指針。我們可以創建一個名爲borrowed_ptr(類似於shared_ptr)的實際類,它可以更好地隱藏原始指針,但在這種情況下它看起來像是矯枉過正。

+0

我喜歡在明確指出非所有權意圖的情況下如何清理此方法。作爲一個側面的問題,爲什麼在一個相當於一個原始指針(基礎類型)的地方使用了std :: move? –

+1

正常的假設(至少在較新的代碼中)應該是原始指針意味着「非擁有」。相反,最好用標記「borrowed_ptr 」的方式標記任何擁有原始指針的「所有者」(並且更容易,因爲應該可能有更少的實例)。這也是指導支持庫(https://github.com/Microsoft/GSL)在今年的CppCon上由Bjarne Stroustrup等人提出的方法。 – villintehaspam

+0

我知道指導原則應該將原始指針視爲非擁有指針。我只是不同意他們。我們將所有這些麻煩都放在我們的代碼中,但在這種情況下,我們只是假設原始指針是非擁有指針。爲什麼不明確說明?我們可以同時擁有「所有者」(或「owned_ptr 」)並且擁有「borrowed_ptr 」,所以這看起來像是我們可以擁有我們的蛋糕並且吃掉它的情況。另外,正如你指出的那樣,這個假設只適用於新開發的代碼。這遺漏了所有現有的代碼,這似乎並不理想。 –

3

傳遞一個原始指針你正在做的方式絕對應該表示沒有所有權正在轉移。

如果您聽說過有人爲說:「不要使用原始指針」,你可能錯過了句子的一部分 - 它應該是「不使用擁有裸指針」,即不應該有一個地方的地方在那裏你有一個你需要調用delete的原始指針。除了可能在一些低級代碼。如果您知道指向的對象超出獲取指針的對象的事實,那麼傳入指針絕對沒有錯。

您的意思是「即將原始指針存儲到TopLevelClass對象的句柄並不能表明Context類不負責管理此指針的生命週期」。 相反,存儲一個原始指針意味着這個 - 「這個對象不管理這個指針指向的對象的生命期」。但在C++ 98風格的代碼中,它並不一定意味着這一點。

使用指針的替代方法是使用引用。這裏有一些注意事項,因爲你必須在構造函數中初始化它,並且它不能像指針那樣設置爲nullptr(這也可能是件好事)。即:

class TopLevelClass { 
public: 
    void someMethod (int someValue) { 
     // Do some work 
    } 

    std::unique_ptr<Context> getContext() { 
     return std::make_unique<Context>(*this); 
    } 
}; 

class Context { 
public: 
    Context(TopLevelClass &tlc) : _tlc(tlc) {} 

    void call (int value) { 
    // Perform some work and then call the top level class... 
    _tlc.someMethod(value); 
    } 

private: 
    TopLevelClass &_tlc; 
}; 

下面是關於該主題的一些文章:

的C++核心準則:

https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#Rr-ptr

一些早期的文章由Herb薩特:

http://herbsutter.com/2013/05/29/gotw-89-solution-smart-pointers/

http://herbsutter.com/2013/05/30/gotw-90-solution-factories/

http://herbsutter.com/2013/06/05/gotw-91-solution-smart-pointer-parameters/

http://herbsutter.com/elements-of-modern-c-style/

有可能也是很多從CppCon以及.cpp的和超越的視頻,但我有點懶得谷歌了合適的人。

+0

謝謝。我同意原始指針不被禁止,但我不同意語義。在我的例子中,我明確指出'TopLevelClass'對象將比'Context'對象長,但是在代碼中我沒有明確說明(我區分了語言的語義和簡單的寫評論)。一般來說,原始指針語義有許多問題:我是否負責刪除對象?如果是這樣,怎麼會這樣('delete'或'delete []')?它會永遠懸垂嗎?對於我的「不要使用原始指針」評論,請參閱[這裏](http://goo.gl/xVn5gI),Ch。 4)。謝謝! –

+1

@unseenghost你永遠不會負責刪除非擁有的原始指針。 –

+0

我同意。目的是我明確表明我不擁有指針。例如,一個原始指針並不表示具有與'std :: unique_ptr'唯一所有權相同的剛性。也就是說,我知道一個'std :: unique_ptr'顯式傳達了唯一的所有權。 –

相關問題