簡體   English   中英

安全分配和復制交換習語

[英]Safe assignment and copy-and-swap idiom

我正在學習c ++,最近我學習了(這里是堆棧溢出)有關復制和交換的習慣用法,我對它有一些問題。 所以,假設我有以下類使用復制和交換習慣用法,例如:

class Foo {
private:
  int * foo;
  int size;

public:
  Foo(size_t size) : size(size) { foo = new int[size](); }
  ~Foo(){delete foo;}

  Foo(Foo const& other){
    size = other.size;
    foo = new int[size];
    copy(other.foo, other.foo + size, foo);
  }

  void swap(Foo& other) { 
    std::swap(foo,  other.foo);  
    std::swap(size, other.size); 
  }

  Foo& operator=(Foo g) { 
    g.swap(*this); 
    return *this; 
  }

  int& operator[] (const int idx) {return foo[idx];}
};

我的問題是,假設我有另一個類,它有一個Foo對象作為數據但沒有指針或其他可能需要自定義復制或賦值的資源:

class Bar {
private:
  Foo bar;
public:
  Bar(Foo foo) : bar(foo) {};
  ~Bar(){};
  Bar(Bar const& other) : bar(other.bar) {}; 
  Bar& operator=(Bar other) {bar = other.bar;}
};

現在我有一系列問題:

  1. 上面為Bar類實現的方法和構造函數是否安全? 使用Foo的復制和交換后,我確保在分配或復制Bar時不會造成任何傷害?

  2. 在復制構造函數和swap中通過引用傳遞參數是必需的嗎?

  3. 是否正確地說當operator=的參數通過值傳遞時,為此參數調用復制構造函數以生成對象的臨時副本,並且該副本隨后與*this交換? 如果我在operator=通過引用傳遞我會有一個大問題,對嗎?

  4. 是否存在這種習慣用法無法在復制和分配Foo提供完全安全的情況?

您應該盡可能在初始化列表中初始化類的成員。 這也將照顧我在評論中告訴你的錯誤。 考慮到這一點,您的代碼變為:

class Foo {
private:
  int size;
  int * foo;

public:
  Foo(size_t size) : size(size), foo(new int[size]) {}
  ~Foo(){delete[] foo;} // note operator delete[], not delete

  Foo(Foo const& other) : size(other.size), foo(new int[other.size]) {
    copy(other.foo, other.foo + size, foo);
  }

  Foo& swap(Foo& other) { 
    std::swap(foo,  other.foo);  
    std::swap(size, other.size); 
    return *this;
  }

  Foo& operator=(Foo g) { 
    return swap(g); 
  }

  int& operator[] (const int idx) {return foo[idx];}
};

class Bar {
private:
  Foo bar;
public:
  Bar(Foo foo) : bar(foo) {};
  ~Bar(){};
  Bar(Bar const& other) : bar(other.bar) { }
  Bar& swap(Bar &other) { bar.swap(other.bar); return *this; }
  Bar& operator=(Bar other) { return swap(other); }
}

整個使用相同的成語

注意

正如在評論中提到的那樣, Bar的自定義拷貝構造函數等是不必要的,但我們假設Bar還有其他的東西:-)

第二個問題

按引用傳遞到swap是必要的,因為這兩種情況都改變了。

需要通過引用傳遞復制構造函數,因為如果按值傳遞,則需要調用復制構造函數

第三個問題

第四個問題

不,但它並不總是最有效的做事方式

1 - 上面為Bar類實現的方法和構造函數是否安全? 使用Foo的復制和交換后,我確保在分配或復制Bar時不會造成任何傷害?

關於復制者:這總是安全的(全有或全無)。 它要么完成(全部),要么拋出異常(沒有)。

如果您的類只由一個成員組成(即沒有基類),賦值運算符將與成員類的安全性一樣安全。 如果您有多個成員,則賦值運算符將不再是“全有或全無”。 第二個成員的賦值運算符可以拋出異常,在這種情況下,對象將被分配為“中途”。 這意味着您需要在新類中再次實現復制和交換以獲得“全部或全部”分配。

但是,在您不會泄漏任何資源的意義上,它仍然是“安全的”。 當然,每個成員的狀態將是一致的 - 只是新類的狀態不一致,因為一個成員被分配而另一個成員沒有被分配。

2 - 在復制構造函數和swap中通過引用傳遞參數是必需的嗎?

是的,通過引用傳遞是強制性的。 復制構造函數是復制對象的構造函數,因此它不能通過值獲取它的參數,因為這意味着必須復制參數。 這將導致無限遞歸。 (拷貝監視器將被調用參數,這意味着要為參數調用copy-ctor,這意味着......)。 對於swap,原因是另一個:如果你要按值傳遞參數,你永遠不能使用swap來真正交換兩個對象的內容 - 交換的“目標”將是最初傳入的對象的副本,哪個會被立即銷毀。

3 - 是否正確地說當operator =的參數通過值傳遞時,為此參數調用復制構造函數以生成對象的臨時副本,並且該副本隨后與* this交換? 如果我在operator =中通過引用傳遞我會有一個大問題,對嗎?

恩,那就對了。 然而,通過引用-const獲取參數,構造本地副本然后與本地副本交換也是很常見的。 然而, by-to-const方式有一些缺點(它禁用了一些優化)。 如果你沒有實現copy-and-swap,你應該通過reference-to-const傳遞。

4 - 在復制和分配Foo時,是否存在這種成語無法提供完全安全性的情況?

我不知道。 當然,總是可以通過多線程(如果沒有正確同步)使事情失敗,但這應該是顯而易見的。

這是一個典型的例子,說明跟隨成語導致不必要的性能懲罰(過早的悲觀化)。 這不是你的錯。 復制和交換習語過於誇張。 一個很好的習語。 但它應該盲從。

注意:您可以在計算機上執行的最昂貴的事情之一是分配和釋放內存。 在實踐中避免這樣做是值得的。

注意:在您的示例中,復制和交換習慣用法總是執行一次釋放,並且通常(當賦值的rhs是左值時)也進行一次分配。

觀察:size() == rhs.size() ,不需要重新分配或分配。 你所要做的就是一份copy 這是很多 ,要快得多。

Foo& operator=(const Foo& g) {
    if (size != g.size)
        Foo(g).swap(*this); 
    else
       copy(other.foo, other.foo + size, foo);
    return *this; 
}

即檢查您可以先回收資源的情況。 如果無法回收資源,請復制並交換(或其他)。

注意:我的評論並不與其他人給出的好答案相矛盾,也沒有任何正確性問題。 我的評論只是一個重大的性能損失。

暫無
暫無

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

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