簡體   English   中英

c ++ unique_ptr參數傳遞

[英]c++ unique_ptr argument passing

假設我有以下代碼:

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)唯一的方法嗎? (我在想rvalue引用,但我不完全理解它們。)

如果你有關於移動語義, unique_ptr等的完整解釋的鏈接,我無法找到,請不要猶豫,分享它。

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

實際上,std :: move只是語法糖。 對於類X的對象x, move(x)與以下內容相同:

static_cast <X&&>(x)

需要這兩個移動函數,因為轉換為右值引用:

  1. 防止函數“add”傳遞值
  2. 使push_back使用B的默認移動構造函數

顯然,如果我改變我的“add”函數以通過引用傳遞(普通左值ref),我不需要在main()使用第二個std::move

我想要對這一切進行一些確認,不過......

我有點驚訝的是,這里沒有非常清楚明確地回答,也沒有在我輕易偶然發現的任何地方。 雖然我對這些東西很陌生,但我認為可以說以下內容。

這種情況是一個調用函數,它構建一個unique_ptr<T>值(可能通過調用new來調用結果),並希望將它傳遞給某個函數,該函數將獲取指向的對象的所有權(通過將其存儲在一個例如,數據結構,這里發生在vector )。 為了表明調用者已獲得所有權,並且已准備好放棄它,傳遞unique_ptr<T>值就緒了。 我可以看到三種傳遞這種價值的合理模式。

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

傳遞const lvalue引用是不合理的,因為它不允許被調用函數獲取所有權(並且const rvalue引用將比那更愚蠢;我甚至不確定它是否被允許)。

就有效代碼而言,選項1和3幾乎是等價的:它們強制調用者將rvalue寫為調用的參數,可能是通過在調用std::move包裝變量(如果參數已經是rvalue)即,無名如從結果鑄造new ,這是沒有必要的)。 但是在選項2中, 不允許傳遞rvalue(可能來自std::move ),並且必須使用命名的unique_ptr<T>變量調用該函數(當從new傳遞unique_ptr<T>時,必須首先賦值給變量) )。

確實使用std::move ,在調用者中保存unique_ptr<T>值的變量在概念上被解除引用(轉換為rvalue,分別轉換為rvalue引用),此時放棄所有權。 在選項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 可以獲得輸出線的幾種排列。

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

所以我的簡單問題是:這是做到這一點的方式,尤其是“移動(b)”是唯一的方法嗎? (我在想rvalue參考,但我不完全理解它...)

如果你有關於移動語義的完整解釋的鏈接,unique_ptr ......我無法找到,請不要猶豫。

無恥插頭 ,搜索標題“搬入成員”。 它准確描述了您的場景。

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

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構造函數參數。

鏈接到工作示例

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM