[英]How to use noexcept in assignment operator with copy-and-swap idiom?
[英]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;}
};
現在我有一系列問題:
上面為Bar
類實現的方法和構造函數是否安全? 使用Foo
的復制和交換后,我確保在分配或復制Bar
時不會造成任何傷害?
在復制構造函數和swap中通過引用傳遞參數是必需的嗎?
是否正確地說當operator=
的參數通過值傳遞時,為此參數調用復制構造函數以生成對象的臨時副本,並且該副本隨后與*this
交換? 如果我在operator=
通過引用傳遞我會有一個大問題,對嗎?
是否存在這種習慣用法無法在復制和分配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.