2014-02-08 45 views
3

我一直試圖通過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 +的每次使用創建新對象,這意味着運算符+的長鏈將具有顯着更好的性能。

我覺得現在正確理解這個左值/右值/移動語義魔法。

+7

您的'operator +'非常不尋常。 –

+3

'operator +'應該返回一個值實例,而不是引用。你已經實現了'operator +',就好像它是'operator + ='一樣。 – WhozCraig

回答

3

getTest(1) + getTest(2) + getTest(3)的結果與返回類型Test::operator+(const Test&)的結果類型相同。它是Test&,因此是一個左值。

operator +通常是一個非成員的過載,通過值返回一個臨時:

Test operator + (const Test& a, const Test& b) 

Test operator + (Test a, const Test& b) 

用於實現operator +=爲成員,並且在執行使用它獎勵積分非會員operator+

+0

我用一些修改過的代碼更新了原來的問題。我想我明白現在發生了什麼。謝謝您的幫助。 – user3286280