我一直試圖通過Bjarne Stroustrup的精彩C++書籍來教自己正確使用C++ 11中的移動語義。我遇到了一個問題 - 移動構造函數未被調用,因爲我期望它是。採取以下代碼:C++ 11移動語義
class Test
{
public:
Test() = delete;
Test(const Test& other) = delete;
Test(const int value) : x(value) { std::cout << "x: " << x << " normal constructor" << std::endl; }
Test(Test&& other) { x = other.x; other.x = 0; std::cout << "x: " << x << " move constructor" << std::endl; }
Test& operator+(const Test& other) { x += other.x; return *this; }
Test& operator=(const Test& other) = delete;
Test& operator=(Test&& other) { x = other.x; other.x = 0; std::cout << "x :" << x << " move assignment" << std::endl; return *this; }
int x;
};
Test getTest(const int value)
{
return Test{ value };
}
int main()
{
Test test = getTest(1) + getTest(2) + getTest(3);
}
此代碼不會編譯 - 因爲我刪除了默認的複製構造函數。添加默認複製構造,控制檯輸出如下:
x: 3 normal constructor
x: 2 normal constructor
x: 1 normal constructor
x: 6 copy constructor
然而,改變的主要功能爲以下:
int main()
{
Test test = std::move(getTest(1) + getTest(2) + getTest(3));
}
產生所需的控制檯輸出:
x: 3 normal constructor
x: 2 normal constructor
x: 1 normal constructor
x: 6 move constructor
這讓我感到困惑,因爲據我瞭解,(getTest(1)+ getTest(2)+ getTest(3))的結果是一個右值(因爲它沒有名字,因此無法在它之後使用是一個指向變量測試),所以它應該使用默認的移動構造函數構造,而不是要求對std :: move()進行顯式調用。
有人可以解釋爲什麼會發生這種行爲?我做錯了什麼?我是否誤解了移動語義學的基礎知識?
謝謝。
編輯1:
我更新了代碼以反映下面的一些評論。
添加類定義:
friend Test operator+(const Test& a, const Test& b) { Test temp = Test{ a.x }; temp += b; std::cout << a.x << " + " << b.x << std::endl; return temp; }
Test& operator+=(const Test& other) { x += other.x; return *this; }
更改主要以:
int main()
{
Test test = getTest(1) + getTest(2) + getTest(4) + getTest(8);
}
這將產生控制檯輸出:
x: 8 normal constructor
x: 4 normal constructor
x: 2 normal constructor
x: 1 normal constructor
x: 1 normal constructor
1 + 2
x: 3 move constructor
x: 3 normal constructor
3 + 4
x: 7 move constructor
x: 7 normal constructor
7 + 8
x: 15 move constructor
我相信這是我應該在這種情況下發生 - 這裏有很多新的對象創建,但更仔細的想法是有道理的,因爲每次調用operator +時,都必須創建一個臨時對象。有趣的是,如果我在發佈模式下編譯修改後的代碼,移動構造函數永遠不會被調用,但是在調試模式下,它被稱爲上面控制檯輸出所描述的。編輯2:
進一步提煉。添加到類定義:
friend Test&& operator+(Test&& a, Test&& b) { b.x += a.x; a.x = 0; return std::move(b); }
可生產的控制檯輸出:
x: 8 normal constructor
x: 4 normal constructor
x: 2 normal constructor
x: 1 normal constructor
x: 15 move constructor
而這恰恰是所需的輸出。
編輯3:
我相信這會更好地做到以下幾點。編輯類定義:
friend Test&& operator+(Test&& a, Test&& b) { b += a; return std::move(b); }
Test& operator+=(const Test& other) { std::cout << x << " += " << other.x << std::endl; x += other.x; return *this; }
這將產生控制檯輸出:
x: 8 normal constructor
x: 4 normal constructor
x: 2 normal constructor
x: 1 normal constructor
2 += 1
4 += 3
8 += 7
x: 15 move constructor
哪個更描述。通過實現rvalue運算符+,不會爲operator +的每次使用創建新對象,這意味着運算符+的長鏈將具有顯着更好的性能。
我覺得現在正確理解這個左值/右值/移動語義魔法。
您的'operator +'非常不尋常。 –
'operator +'應該返回一個值實例,而不是引用。你已經實現了'operator +',就好像它是'operator + ='一樣。 – WhozCraig