![](/img/trans.png)
[英]Is this a proper implementation of the Rule of Five (or Rule of Four and 1/2)?
[英]What is the Rule of Four (and a half)?
為了正確處理 object 復制,經驗法則是三法則。 對於 C++11,移動語義是一回事,所以它是五法則。 但是,在這里和互聯網上的討論中,我還看到了對四規則(半)的引用,它是五規則和復制和交換習語的組合。
那么究竟什么是四法則(半法則)? 需要實現哪些功能,每個功能的主體應該是什么樣的? 哪個function是一半? 與五法則相比,這種方法有什么缺點或警告嗎?
這是一個類似於我當前代碼的參考實現。 如果這是不正確的,正確的實現會是什么樣子?
//I understand that in this example, I could just use `std::unique_ptr`.
//Just assume it's a more complex resource.
#include <utility>
class Foo {
public:
//We must have a default constructor so we can swap during copy construction.
//It need not be useful, but it should be swappable and deconstructable.
//It can be private, if it's not truly a valid state for the object.
Foo() : resource(nullptr) {}
//Normal constructor, acquire resource
Foo(int value) : resource(new int(value)) {}
//Copy constructor
Foo(Foo const& other) {
//Copy the resource here.
resource = new int(*other.resource);
}
//Move constructor
//Delegates to default constructor to put us in safe state.
Foo(Foo&& other) : Foo() {
swap(other);
}
//Assignment
Foo& operator=(Foo other) {
swap(other);
return *this;
}
//Destructor
~Foo() {
//Free the resource here.
//We must handle the default state that can appear from the copy ctor.
//(The if is not technically needed here. `delete nullptr` is safe.)
if (resource != nullptr) delete resource;
}
//Swap
void swap(Foo& other) {
using std::swap;
//Swap the resource between instances here.
swap(resource, other.resource);
}
//Swap for ADL
friend void swap(Foo& left, Foo& right) {
left.swap(right);
}
private:
int* resource;
};
那么四分之一(一半)究竟是什么呢?
“四大(一半)規則”指出,如果你實施其中一個
那么你必須有一個關於其他人的政策。
需要實現哪些功能,每個功能的主體應該是什么樣的?
移動構造函數(使用默認構造函數和交換):
S(S&& s) : S{} { swap(*this, s); }
賦值運算符(使用構造函數和交換)
S& operator=(S s) { swap(*this, s); }
析構函數(資源的深層副本)
std::swap
使用move(或copy)構造函數,這將導致無限遞歸。 一半的功能是什么?
從上一篇文章:
“為了實現Copy-Swap習慣用法,你的資源管理類還必須實現一個swap()函數來執行逐個成員的交換(這是你的”......(和一半)“)
所以swap
方法。
與五法則相比,這種方法有任何缺點或警告嗎?
我已經寫過的警告是要編寫正確的交換以避免無限遞歸。
與五法則相比,這種方法有任何缺點或警告嗎?
雖然它可以節省代碼重復,但使用復制和交換只會導致更糟糕的類,更直接。 你正在傷害你的班級表現,包括移動任務(如果你使用統一分配操作員,我也不是它的粉絲),這應該非常快。 作為交換,您將獲得強大的異常保證,這在一開始看起來不錯。 問題是,您可以通過簡單的通用函數從任何類中獲得強大的異常保證:
template <class T>
void copy_and_swap(T& target, T source) {
using std::swap;
swap(target, std::move(source));
}
就是這樣。 因此,需要強大異常安全的人無論如何都可以獲得它。 坦率地說,強大的異常安全無論如何都是一個利基市場。
保存代碼重復的真正方法是通過Zero of Zero:選擇成員變量,這樣就不需要編寫任何特殊功能。 在現實生活中,我會說90%以上的時間我看到特殊的會員功能,他們可以很容易地避免。 即使你的類確實有某種一個特殊的成員函數需要特殊的邏輯,你通常是關閉向下推到一個成員更好。 您的記錄器類可能需要在其析構函數中刷新緩沖區,但這不是編寫析構函數的理由:編寫一個處理刷新的小緩沖區類,並將其作為記錄器的成員。 記錄器可能具有可以自動處理的各種其他資源,並且您希望讓編譯器自動生成復制/移動/破壞代碼。
關於C ++的事情是每個函數自動生成特殊函數是全部或全部。 這就是拷貝構造函數(如)要么被自動生成的,同時考慮到所有成員,或者你必須手工編寫(更糟的是,維持)這一切 。 所以它強烈推動你采取一種向下推動事物的方法。
如果您正在編寫一個類來管理資源並需要處理它,通常應該是:a)相對較小,b)相對通用/可重用。 前者意味着一些重復的代碼並不是什么大問題,而后者意味着您可能不希望將性能留在桌面上。
總而言之,我強烈反對使用復制和交換,以及使用統一賦值運算符。 嘗試遵循零規則,如果你不能,遵循五法則。 只有當你可以使它比通用交換(執行3次移動)更快時才寫swap
,但通常你不需要打擾。
簡單來說,只要記住這一點。
0 規則:
Classes have neither custom destructors, copy/move constructors or copy/move assignment operators.
規則 3 :如果您實現了其中任何一個的自定義版本,那么您就實現了所有這些。
Destructor, Copy constructor, copy assignment
5 規則:如果您實現自定義移動構造函數或移動賦值運算符,則需要定義所有 5 個。 需要移動語義。
Destructor, Copy constructor, copy assignment, move constructor, move assignment
四個半規則:與規則 5 相同,但具有復制和交換習語。 通過包含交換方法,復制賦值和移動賦值合並為一個賦值運算符。
Destructor, Copy constructor, move constructor, assignment, swap (the half part)
Destructor: ~Class();
Copy constructor: Class(Class &);
Move constructor: Class(Class &&);
Assignment: Class & operator = (Class);
Swap: void swap(Class &);
沒有警告,優點是分配速度更快,因為按值復制實際上比在方法主體中創建臨時 object 更有效。
現在我們有了臨時 object,我們只需對臨時 object 執行交換。 當它超出 scope 時,它會自動銷毀,現在我們在 object 中擁有運算符右側的值。
參考資料:
https://www.linkedin.com/learning/c-plus-plus-advanced-topics/rule-of-five?u=67551194 https://en.cppreference.com/w/cpp/language/rule_of_three
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.