2015-02-23 103 views
21

假設我有一個由std::unique_ptr管理的對象。我的代碼的其他部分需要訪問此對象。什麼是傳遞指針的正確解決方案?我是否應該通過std::unique_ptr::get傳遞普通指針,還是應該使用並傳遞std::shared_ptr而不是std::unique_ptr我應該使用std :: shared指針傳遞一個指針嗎?

我對std::unique_ptr有一些偏好,因爲該指針的所有者實際上負責清理。如果我使用共享指針,那麼即使實際上應該銷燬它,由於共享指針,對象仍有可能保持活動狀態。

編輯:不幸的是,我忘了指出,指針不會只是一個函數調用的參數,但它會被存儲在其他對象來建立對象的網絡結構。我不喜歡共享指針,因爲它不再清楚,誰實際上擁有該對象。

+1

爲什麼不傳遞unique_ptr對象的引用? – Zaiborg 2015-02-23 10:57:21

+0

你不能有'unique_ptr'和'shared_ptr'管理同一個對象。 – 2015-02-23 10:58:13

+3

將引用傳遞給引用的對象,這是一個好主意。 – 2015-02-23 10:59:14

回答

18

如果被管理對象的所有權未被轉移(並且因爲它是unique_ptr,所有權不能共享),那麼將被調用函數中的邏輯與所有權概念分開更爲正確。我們通過引用來呼叫。

這是說的一個令人費解的方式:

考慮:

std::unique_ptr<Thing> thing_ptr; 

改變的事情:

// declaration 
void doSomethingWith(Thing& thing); 

// called like this 
doSomethingWith(*thing_ptr); 

用的東西,而無需修改它。

// declaration 
void doSomethingWith(const Thing& thing); 

// called like this 
doSomethingWith(*thing_ptr); 

唯一一次你想要提unique_ptr在函數簽名是,如果你是轉移所有權:

// declaration 
void takeMyThing(std::unique_ptr<Thing> p); 

// call site 
takeMyThing(std::move(thing_ptr)); 

你永遠需要做到這一點:

void useMyThing(const std::unique_ptr<Thing>& p); 

這是一個壞主意的原因是,如果將useMyThing的邏輯與所有權的概念相混淆,從而縮小了重用的範圍。

考慮:

useMyThing(const Thing& thing); 

Thing x; 
std::unique_ptr<Thing> thing_ptr = makeAThing(); 
useMyThing(x); 
useMyThing(*thing_ptr); 

更新:

注意到更新的問題 - 存儲(無所屬)到該對象的引用。

這樣做的一種方法的確是存儲指針。然而,指針受到邏輯錯誤的可能性,因爲它們可以合法地爲空。指針的另一個問題是它們不能很好地與std:: algorithms和容器搭配使用 - 需要自定義比較函數等。

有做這個std::-compliant方式 - std::reference_wrapper<>

所以,與其這樣:

std::vector<Thing*> my_thing_ptrs; 

做到這一點:

std::vector<std::reference_wrapper<Thing>> my_thing_refs; 

由於std::reference_wrapper<T>定義操作T&,你可以在期望T的任何表達式中使用reference_wrapped對象。

例如:

std::unique_ptr<Thing> t1 = make_thing(); 
std::unique_ptr<Thing> t2 = make_thing(); 
std::unique_ptr<Thing> t3 = make_thing(); 

std::vector<std::reference_wrapper<const Thing>> thing_cache; 

store_thing(*t1); 
store_thing(*t2); 
store_thing(*t3); 

int total = 0; 
for(const auto& t : thing_cache) { 
    total += value_of_thing(t); 
} 

其中:

void store_thing(const Thing& t) { 
    thing_cache.push_back(std::cref(t)); 
} 

int value_of_thing(const Thing& t) { 
    return <some calculation on t>; 
} 
+0

這是一個非常好的答案,非常感謝!不幸的是,我忘了指出一個重要的事實,我現在編輯到我的文章中。 – Michael 2015-02-23 11:50:24

+0

@Michael我認爲,即使代碼的其他部分希望堅持引用這個建議仍然存在。只要你可以確保'unique_ptr'被銷燬後代碼的其他部分不會使用它。如果你不能確保那麼'unique_ptr'不合適。 – 2015-02-23 11:57:52

6

通常,您只需將引用或純指針傳遞給希望觀察對象的代碼的其他部分。

通過引用傳遞:

void func(const Foo& foo); 

std::unique_ptr<Foo> ptr; 

// allocate ptr... 

if(ptr) 
    func(*ptr); 

路過原始指針:

void func(const Foo* foo); 

std::unique_ptr<Foo> ptr; 

// allocate ptr... 

func(ptr.get()); 

的選擇將取決於需要傳遞一個空指針。

您有責任確保旁觀者在unique_ptr被銷燬後不使用指針或引用。如果您不能保證那麼您必須使用shared_ptr而不是unique_ptr。觀察員可以持有weak_ptr表明他們沒有所有權。

編輯:即使觀察者希望堅持指針或引用是好的但它確實使它更難以確保unique_ptr已被銷燬後不會被使用。

+0

我想你在這裏用「你的責任」來解決問題。對可能已故的對象持有refs是通信不良的標誌(應該通知數據結構已經刪除了一個元素)。事實上,weak_ptr可能是一個便宜的出路。 – 2016-01-08 15:44:29

3

由於問題的更新,我寧願:

  1. std::reference_wrapper<>由理查德·霍奇斯認爲,只要你不需要NULL值的選項。

    請注意,您的用例似乎需要一個默認的構造函數,所以這是不可用的,除非您有一些公共靜態實例用作默認值。

  2. 一些自定義not_your_pointer<T>類型與所需的語義:默認構建,可複製和可轉讓,可從unique_ptr和非擁有轉換。如果你使用它很可能只是值得的,因爲寫一個新的智能指針類需要一些思考。

  3. ,如果你需要處理懸空引用擺好,用std::weak_ptr和所有權變更到shared_ptr(也就是說,只有一個 shared_ptr的存在:有沒有關於所有權和對象生存歧義不受影響)


問題更新之前,我寧願:

  1. 表明,所有權不被傳遞引用傳遞:

    void foo(T &obj); // no-one expects this to transfer ownership 
    

    稱爲:

    foo(*ptr); 
    

    你失去通過nullptr雖然能力,如果該事項。

  2. 表明,所有權不被明確禁止其轉移:

    void foo(std::unique_ptr<T> const &p) { 
        // can't move or reset p to take ownership 
        // still get non-const access to T 
    } 
    

    這是明確的,但它暴露你的內存管理政策到被叫方。

  3. 傳遞一個原始指針和文件,所有權不應該被轉移。這是最弱和最容易出錯的。不要這樣做。

+0

什麼'const unique_ptr&'提供一個原始指針不?正如你所說,缺點是它暴露你的記憶管理政策? – 2015-02-23 11:30:30

+0

const-ref-to-unique_ptr使代碼明確地文檔化並保證合同。被調用者不太可能會認爲他們應該(或允許)擁有所有權,他們將不得不使用'const_cast'或其他一些破解來獲取它。與原始引用不同,您仍然可以傳遞'nullptr'。 – Useless 2015-02-23 11:34:02

+0

@Useless:'const unique_ptr&'規範中的任何內容都不能通過'p.get()'來獲取底層的原始指針,並且隨便做些什麼(在數據結構中存儲是OP的意圖)。在傳遞原始指針或引用時的默認假設是不會傳遞所有權(考慮所有那些採用'const char *'的函數),所以選項2實際上並不會讓您購買太多。主要觀點非常清楚地記錄指針在它被存儲的位置不是擁有的。 – 2015-02-23 14:31:31

4

不要過分關注來電者的要求。這個函數需要傳入什麼?如果只在通話期間使用該對象,則使用參考。

void func(const Foo& foo); 

如果需要保持過去的持續時間對象的所有權,然後傳遞一個shared_ptr

void func(std::shared_ptr<Foo> foo); 
+1

考慮到問題以「假設我有一個由std :: unique_ptr管理的對象」開頭,最後一部分可能會更清楚。 – 2015-02-23 14:49:12

0

該地已在GotW #91 - Smart Pointer Parameters覆蓋香草薩特。它也涉及性能方面,而其他答案雖然很好,但重點關注內存管理。

以上建議有人推薦將智能指針作爲常量引用 - 在稍後的編輯中改變了主意。我們有代碼被濫用 - 它使簽名變得更聰明 - 即更復雜但沒有好處。最糟糕的情況是,一名初級開發人員接手時 - 他不會像@邁克爾一樣質疑,並且從一個不好的例子中「學習」如何去做。它的作品,但...

對我來說,智能指針參數傳遞的問題是嚴重的,在解釋什麼是智能指針後立即在任何書/文章/帖子中突出提及。

現在想知道如果類似lint的程序應該標記作爲樣式元素的const引用傳遞。