2012-09-22 245 views
11

假設我有以下代碼:C++的unique_ptr參數

class B { /* */ }; 

class A { 
    vector<B*> vb; 
public: 
    void add(B* b) { vb.push_back(b); } 
}; 


int main() { 

    A a; 
    B* b(new B()); 
    a.add(b); 
} 

假設在這種情況下,所有的原始指針B*可以通過unique_ptr<B>處理。

令人驚訝的是,我無法找到如何使用unique_ptr轉換此代碼。試了幾次後,我想出了下面的代碼,它編譯:

class A { 
    vector<unique_ptr<B>> vb; 
public: 
    void add(unique_ptr<B> b) { vb.push_back(move(b)); } 
}; 


int main() { 

    A a; 
    unique_ptr<B> b(new B()); 
    a.add(move(b)); 
} 

所以我簡單的問題:這是做到這一點,特別是方式,是move(b)做到這一點的唯一途徑? (我正在考慮右值引用,但我沒有完全理解它們。)

如果你有一個鏈接和移動語義的完整解釋,unique_ptr等,我沒有找到,請不要猶豫分享它。

編輯根據http://thbecker.net/articles/rvalue_references/section_01.html,我的代碼似乎沒問題。

實際上,std :: move只是語法糖。與類X的對象x,作爲move(x)是一樣的:

static_cast <X&&>(x) 

需要這些2移動功能,因爲投射到一個右值參考:

  1. 防止函數從由值
  2. 傳遞「添加」
  3. 使得push_back使用B的默認移動構造函數

顯然,我不需要在第二std::movemain()如果我改變我的「添加」功能通過引用(普通左值參考)。

我想這一切一定確認,但...

+0

我認爲這可能對您有用:http:// stackoverflow。com/questions/2876641/so-can-unique-ptr-be-used-safely-in-stl-collections –

+0

@KirilKirov謝謝,這個問題給出了有趣的鏈接(參見編輯) –

+0

你可以避免'std :: move'在主要的情況下,如果你改變了你的add,那會很糟糕,因爲'add'的調用者不知道他們是否仍然擁有這個對象,或者沒有查看'add'的來源。 – Puppy

回答

8

是的,這是應該怎麼做。您明確將所有權從main轉移到A。這與之前的代碼基本相同,除了它更明確且更可靠。

+0

一些擴展的討論:[ Herb Sutter的GOTW#91](http://herbsutter.com/2013/06/05/gotw-91-solution-smart-pointer-parameters/)以及[斯科特邁耶斯關於傳遞移動類型](http:///scottmeyers.blogspot.com/2014/07/should-move-only-types-ever-be-passed.html)明確地「按價值」與「按r值參考」 –

9

我有些驚訝,這在這裏沒有很清楚明確地回答,也沒有在任何我輕易發現的地方回答。雖然我對這件事很陌生,但我認爲可以這樣說。

的情況是,(通過強制從到new調用的結果可能)建立了一個unique_ptr<T>值調用函數,並希望將它傳遞給一些功能,將採取指向對象的所有權,以(通過將其存儲在例如一個數據結構,正如這裏發生的vector)。爲了表明主叫方已獲得所有權,並準備放棄它,傳遞unique_ptr<T>值已到位。儘管我可以看到三種合理的傳遞這種價值的模式。

  1. 按值傳遞,如在問題中的add(unique_ptr<B> b)
  2. 通過非const左值參考在add(unique_ptr<B>& b)
  3. 傳傳遞,如通過右值參考,如在add(unique_ptr<B>&& b)

路過const左值參考不會是合理的,因爲它不允許被調用的函數,以取得所有權(和const右值引用會比這更愚蠢;我甚至不確定它是否被允許)。

就有效的代碼而言,選項1和3幾乎是等價的:它們強制調用者爲調用寫入一個右值作爲參數,可能通過在調用std::move時調用一個變量(如果參數已經是rvalue,也就是的結果中不存在名稱,這不是必需的)。然而,在選項2中,傳遞一個右值(可能來自std::move)是不允許,並且該函數必須用名爲unique_ptr<T>變量(當從new傳遞一個變量時,必須首先分配給變量)調用該函數。

當確實使用std::move時,在調用者中保留unique_ptr<T>值的變量在概念上被取消引用(轉換爲右值,分別轉換爲右值引用),並且此時放棄所有權。在選項1中,取消引用是真實的,並且該值被移動到傳遞給被調用函數的臨時對象(如果calles函數將檢查調用者中的變量,它會發現它已經包含空指針)。所有權已轉移,調用者無法決定不接受它(對參數無所作爲導致指向值在函數退出時被銷燬;在參數上調用release方法會阻止此操作,但會只是導致內存泄漏)。令人驚訝的是,在函數調用期間,選項2和3的語義上是,儘管它們需要不同的調用者語法。如果被調用函數將參數傳遞給另一個採用右值的函數(如push_back方法),則必須在兩種情況下都插入std::move,這會在該點轉移所有權。如果被調用的函數忘記對參數做任何事情,那麼調用者會發現自己仍然擁有該對象(如果爲其命名)(在選項2中是強制性的);儘管如此,在情況3中,由於函數原型要求調用者同意釋放所有權(通過調用std::move或提供臨時的)。總之,這些方法可以做

  1. 強制呼叫者放棄所有權,並且一定要實際聲明它。
  2. 部隊打電話給擁有所有權,並準備(通過提供非const參考)放棄它;然而這並不明確(不需要呼叫std::move,甚至不允許),也不會剝奪所有權。我認爲這種方法的意圖還不太清楚,除非明確意圖取得所有權是否由被叫功能決定(某些用途可以想象,但呼叫者需要知道)
  3. 強制呼叫者明確指示放棄所有權,如同1.(但所有權的實際轉移被推遲到函數調用之後)。

方案3的意圖相當明確;提供所有權實際上是採取,這對我來說是最好的解決方案。它比1更有效率,因爲沒有指針值被移動到臨時對象(std::move的調用實際上只是強制轉換並且不需要任何成本);如果在實際移動其內容之前指針經過幾箇中間函數,這可能尤其相關。

這裏有一些代碼可供嘗試。

class B 
{ 
    unsigned long val; 
public: 
    B(const unsigned long& x) : val(x) 
    { std::cout << "storing " << x << std::endl;} 
    ~B() { std::cout << "dropping " << val << std::endl;} 
}; 

typedef std::unique_ptr<B> B_ptr; 

class A { 
    std::vector<B_ptr> vb; 
public: 
    void add(B_ptr&& b) 
    { vb.push_back(std::move(b)); } // or even better use emplace_back 
}; 


void f() { 
    A a; 
    B_ptr b(new B(123)),c; 
    a.add(std::move(b)); 
    std::cout << "---" <<std::endl; 
    a.add(B_ptr(new B(4567))); // unnamed argument does not need std::move 
} 

作爲寫入,輸出是

storing 123 
--- 
storing 4567 
dropping 123 
dropping 4567 

注意,值在有序存儲在矢量被破壞。嘗試更改方法add的原型(如果需要,則調整其他代碼以使其編譯),以及它是否實際傳遞其參數b。可以獲得輸出線的幾種排列。

0

所以我簡單的問題是:這是做到這一點的方法,特別是,這是「移動(b)」的唯一方法嗎? (我正在考慮右值引用,但我並不完全瞭解它,所以...)

如果你有一個完整的移動語義解釋的鏈接,unique_ptr ...我無法找到,don毫不猶豫。

Shameless plug,搜索標題「搬入會員」。它描述了你的場景。

0

你在main代碼可以簡化一點,因爲C++ 14:

a.add(make_unique<B>()); 

,你可以把對內部括號內B的構造函數的參數。


你也可以考慮一個類的成員函數,它接受一個原始指針的所有權:

void take(B *ptr) { vb.emplace_back(ptr); } 

main相應的代碼是:

a.take(new B()); 

另一個選項是使用完美轉發添加矢量成員:

template<typename... Args> 
void emplace(Args&&... args) 
{ 
    vb.emplace_back(std::make_unique<B>(std::forward<Args>(args)...)); 
} 

和代碼主:

a.emplace(); 

,其中,像以前一樣,你可以把構造函數參數爲B括號內。

Link to working example