2015-01-13 55 views
2

考慮下面的示例代碼:我是否應該在類構造函數內部或外部初始化shared_ptr?

#include <memory> 

class Foo { 
public: 
    Foo(std::shared_ptr<int> p); 
private: 
    std::shared_ptr<int> ptr; 
}; 

Foo::Foo(std::shared_ptr<int> p) : ptr(std::move(p)) { 
} 

class Bar { 
public: 
    Bar(int &p); 
private: 
    std::shared_ptr<int> ptr; 
}; 

Bar::Bar(int &p) : ptr(std::make_shared<int>(p)) { 
} 

int main() { 
    Foo foo(std::make_shared<int>(int(256))); 
    Bar bar(*new int(512)); 

    return 0; 
} 

兩個Foo和Bar正常工作。但是,在調用構造函數時創建shared_ptr,然後將所有權與std :: move一起傳遞,並僅將對象的引用傳遞給對象並將shared_ptr的創建委託給類構造函數,是否有任何區別?

我認爲第二種方式更好,因爲我不必移動指針。但是,我主要看到了我正在閱讀的代碼中使用的第一種方式。

我應該使用哪一個,爲什麼?

+1

我更喜歡用'std :: make_shared'進行直接初始化。如果'new'拋出一個確定的內存泄漏。 –

+2

代碼泄漏內存,這兩件事情完全不同 –

回答

6

Foo是正確的。

酒吧是可憎的。它涉及內存泄漏,不安全的異常行爲和不必要的副本。

編輯:內存泄漏的解釋。

解構這一行:

Bar bar(*new int(512)); 

結果在這些操作:

  1. 呼叫新INT(512),這導致在一個調用操作者新的,在堆上返回一個指針爲int (內存分配)。
  2. 取消引用指針,以便爲構造函數提供const引用
  3. 然後Bar構造它的shared_ptr,其中一個由make_shared返回(此部分是有效的)。這個shared_ptr的int用引用傳遞的int的副本初始化。
  4. 然後函數返回,但由於沒有變量記錄了從new返回的指針,所以沒有東西可以釋放內存。爲了銷燬對象並釋放其內存,每一個新的操作都必須通過刪除鏡像。
  5. 因此內存泄露
+0

你能更好地論證「內存泄漏」和「exeption unsafety」嗎? –

+0

我會編輯答案 –

+0

這很好,但問題不在Bar本身,而是它被使用的方式。但是,你聲明這個錯誤是Bar,而真正的問題是* new(這使得指針返回新的不可用,因此不可刪除) –

3

這取決於你想要達到的目標。

如果你在內部需要一個shared_ptr,因爲你想與你創建的其他對象共享對象,第二種方式可能會更好(除非那個可怕的構造函數明顯)。

如果您希望共享現有對象的所有權(這是更常見的情況),您沒有選擇權,而需要使用第一種方法。

如果這些都不適用,那麼您可能首先不需要shared_ptr

1

第二種方法是不正確的;它泄漏內存。與

Bar::Bar(int &p) : ptr(std::make_shared<int>(p)) { 
} 

.... 

Bar bar(*new int(512)); 

shared_ptr的通過std::make_shared製成需要的p的值(這是512)建立一個新的共享指針這是負責新一塊內存;它不承擔p所在的內存地址的責任。這件作品 - 您在main中分配的那件作品 - 則會丟失。這段代碼可以使用

     // +---------------------- building a shared_ptr directly 
         // v    v----- from p's address 
Bar::Bar(int &p) : ptr(std::shared_ptr<int>(&p)) { 

......但看看那個。 這是純粹的邪惡。沒有人期望構造函數的引用參數要求它引用新對象將承擔責任的堆對象。

你可以更三立寫

Bar::Bar(int *p) : ptr(p) { 
} 

.... 

Bar bar(new int(512)); 

事實上,你可以給Foo,做的是,如果你想第二個構造函數。這是一個參數,它有多清晰,函數簽名使得指針必須是堆分配對象,但std::shared_ptr提供了相同的內容,所以有先例。這取決於你的班級做什麼,這是否是一個好主意。

+0

爲什麼讓用戶在第一時間做堆分配?您可以通過非擁有(右值)引用輕鬆地獲取參數,並將其複製(移動)到「Bar」情況下的'shared_ptr'中。 – ComicSansMS

+0

你可能想要擁有它的一個原因是你需要從std :: unique_ptr的所有權中獲得所有權,或者處理使用'std :: auto_ptr'或'boost :: scoped_ptr'的遺留代碼。有時候你不能自由選擇你的圖書館,然後像這樣的東西可以減少樣板代碼。這可能不是你經常使用的工具,但它可以合理安排。有時。 – Wintermute

+0

好點。然而,在那種情況下,我認爲'Foo'方法更可取,這使得所有權被傳遞給對象的構造函數變得更加明顯。儘管如此,關於爲什麼「Bar」的原始代碼被破壞的很好的分析。 – ComicSansMS

0

兩者都可以正常工作,但在main中使用它們的方式並不一致。

當我看到一個構造函數接受一個引用(如Bar(int& p)),我期望Bar擁有一個引用。當我看到Bar(const int& p)我希望它保留一份副本。 當我看到一個右值裁判(不具有普遍性,像Bar(int&& p)我期待p不是「倖存的內容」通過後。(嗯......對於它,這不是有意義的......)。

在任何情況下p持有int,而不是一個pointer,什麼make_shared預計是參數轉發到int構造函數(即...一個int,不是int *)。

你的主要所以必須

Foo foo(std::make_shared<int>(int(256))); 
Bar bar(512); 

這將使欄保存一個動態分配的值的可共享副本512

如果你這樣做Bar bar(*new int(512))你會讓你保存你的「new int」的副本,它的指針會被丟棄,因此int本身就會泄漏。

一般情況下,像*new something表達聽起來應該是「沒有沒有沒有沒有沒有......」

但你Bar構造有一個問題:要能採取常量或表達式返回值,則必須採取const int&,而不是int&

0

如果你的意圖是採取一個堆分配對象的唯一所有權,我建議你接受std::unique_ptr。它清楚地記錄了意向,你可以創建一個從std::unique_ptr一個std::shared_ptr內部,如果你需要:

#include <memory> 

class Foo { 
public: 
    Foo(std::unique_ptr<int> p); 
private: 
    std::shared_ptr<int> ptr; 
}; 

Foo::Foo(std::unique_ptr<int> p) : ptr(std::move(p)) { 
} 

int main() { 
    Foo foo(std::make_unique<int>(512)); 
} 

不要做Bar,很容易出錯,未能描述你的意圖(如果我理解正確你的意圖)。

0

假設您僅僅使用int作爲示例,並且有一個實際的資源,則取決於您想要實現的目標。

第一種是典型的依賴注入,其中通過構造函數注入對象。好的一面是它可以簡化單元測試。

第二種情況只是在構造函數中創建對象,並使用通過構造函數傳遞的值來初始化它。


順便說一句,小心你如何初始化。此:

Bar bar(*new int(512)); 

導致內存泄漏。內存已分配,但從未釋放。

相關問題