2016-03-12 51 views
0

我想了解如何完美轉發的作品,但我不明白爲什麼拷貝構造函數被調用下面的代碼中不明白爲什麼完美轉發不工作

#include <utility> 
#include <iostream> 
using std::cout; 
using std::endl; 

class Something { 
public: 
    Something() = default; 
    Something(__attribute__((unused)) const Something& other) { 
     cout << "Copy constructor called" << endl; 
    } 
    Something(__attribute__((unused)) Something&& other) { 
     cout << "Move constructor called" << endl; 
    } 

    void print() { 
     cout << "Something::print() called" << endl; 
    } 
}; 

void function_1(Something&& one) { 
    cout << "version two called" << endl; 
    Something inner{one}; 
    inner.print(); 
} 
void function_1(const Something& one) { 
    Something inner(one); 
    inner.print(); 
} 

template <typename... T> 
void test_function(T&&... ts) { 
    function_1(std::forward<T>(ts)...); 
} 

int main() { 

    const Something some1 {Something()}; 

    test_function(some1); 
    test_function(Something()); 

    return 0; 
} 

這將產生以下輸出

Copy constructor called 
Something::print() called 
version two called 
Copy constructor called 
Something::print() called 

更改代碼以在右值引用中包含std::move,但我並不期望需要它。當一個引用是一個右值引用時,正確的構造函數應該被自動調用嗎?正確的引用已解決,但正在調用錯誤的構造函數。任何幫助將不勝感激!

+1

這應該很容易理解:只需在構造函數中設置一個斷點,當你打開它時,回溯會告訴你它是如何被調用的。這是調試器可以解決的最簡單的問題之一。 –

+0

@SamVarshavchik我明白它是如何被調用的,但錯誤的超載被稱爲! – Curious

+0

你怎麼說?你知道你需要明確地使用std :: move()來保存右值引用語義,對嗎? –

回答

4

右值引用綁定爲右值。它本身並不是一個右值,因爲它有一個名字。

但是,在使用點名稱的任何內容默認情況下都是左值,甚至是右值引用。你的代碼可以使用Something&& one三次,如果第一次使用隱含move s你會被擰緊。

相反,它是使用點(默認情況下)的左值,並且它綁定到右值。

當你想表示你不再需要它的狀態持續存在時,std::move它。

完美的轉發可以用來寫你的function_1 s,把std::forward<Blah>(blah)放在你想從blah移動的地方,如果它是一個右值引用。


現在上面充滿了謊言,因爲有xvalues prvalues左值等 - 標準更復雜。例如,在返回語句中使用變量可以將命名值轉換爲右值。但基本的經驗法則是值得了解的:它有一個名稱,它是一個左值(除非明確鑄造或過期)。

+0

你能解釋一下嗎?「但是任何名稱在使用時的默認值都是左值,即使是右值引用。你的代碼可以使用三次&&&三次,如果第一次使用隱式移動,你將會被搞砸。再來一點?對不起,我很難理解這個...... – Curious

+1

@curious確實'Something && one'有一個名字?是?然後它是一個左值。想象一下你做了'auto x = one; std :: cout << one << one; return x;' - 這3箇中的哪一個應該移動? Rvalues是沒有名字的東西(所以*不能被重用),或者顯式地賦值給右值(所以程序員錯誤),或者在簡單的return語句(super-NRVO)中返回本地值。 – Yakk

+0

哦......這與我之前錯誤的理解完全矛盾! – Curious

1

此代碼將調用複製ctor,而不是移動ctor。

void function_1(Something&& one) { 
    cout << "version two called" << endl; 
    Something inner{one}; 
    inner.print(); 
} 

此代碼調用move ctor。

void function_1(Something&& one) { 
    cout << "version two called" << endl; 
    Something inner{std::move(one)}; 
    inner.print(); 
} 

表達式one在技術上是一個l值。它指的是一個右值引用。但要真正得到右值引用,你必須使用std::move。一般來說,任何名稱都是l值。無名臨時對象,就像你Something()表達main()

test_function(Something()); 

可右值的,並且可以調用移動,而無需使用std::move

+0

但是爲什麼複製構造函數被調用?我不認爲我完全理解究竟發生了什麼。 'one'是一個右值。那麼爲什麼不會調用右值構造函數呢? – Curious

+1

發生了什麼事是''test_function(Something())'導致'version two called'行。 (在'function_1(Something &&)''的實現的第一行)。然後複製發生。在'內心{一個};'。在'test_function(Something())'中,它正確地將它作爲r值引用匹配,否則你不會看到'version two called'。只有當'inner'被實例化時纔會得到副本。 –

+0

我想標記兩個答案是正確的,但不幸的是我不能這樣做。我會上傳你的答案給你一個同等的積分獎勵! – Curious

相關問題