[英]Copy ctor is called instead of move ctor - Can compiler give a warning?
[英]Copy ctor called instead of move ctor
為什么從bar
而不是move構造函數返回時調用了復制構造函數?
#include <iostream>
using namespace std;
class Alpha {
public:
Alpha() { cout << "ctor" << endl; }
Alpha(Alpha &) { cout << "copy ctor" << endl; }
Alpha(Alpha &&) { cout << "move ctor" << endl; }
Alpha &operator=(Alpha &) { cout << "copy asgn op" << endl; }
Alpha &operator=(Alpha &&) { cout << "move asgn op" << endl; }
};
Alpha foo(Alpha a) {
return a; // Move ctor is called (expected).
}
Alpha bar(Alpha &&a) {
return a; // Copy ctor is called (unexpected).
}
int main() {
Alpha a, b;
a = foo(a);
a = foo(Alpha());
a = bar(Alpha());
b = a;
return 0;
}
如果bar
確實return move(a)
則行為符合預期。 我不明白為什么調用std::move
是必要的,因為foo
在返回時會調用move構造函數。
在這種情況下有兩件事需要理解:
a
in bar(Alpha &&a)
是一個命名的右值參考; 因此,被視為左值。 a
仍然是一個參考。 第1部分
由於a
在bar(Alpha &&a)
是一個命名右值引用,其作為左值處理。 將命名的右值引用視為左值的動機是提供安全性。 考慮以下,
Alpha bar(Alpha &&a) {
baz(a);
qux(a);
return a;
}
如果baz(a)
將a
視為rvalue,則可以自由調用move構造函數,而qux(a)
可能無效。 該標准通過將命名的右值引用視為左值來避免此問題。
第2部分
由於a
仍然是引用(並且可以引用bar
范圍之外的對象), bar
在返回時調用復制構造函數。 這種行為背后的動機是提供安全。
參考
是的,非常困惑。 我想舉另一個SO張貼在這里implicite移動 。 在哪里我發現以下評論有點令人信服,
因此,標准委員會決定您必須明確任何命名變量的移動,無論其引用類型如何
實際上“&&”已經表示放手了,當你做“返回”時,它就足夠安全了。
可能它只是標准委員會的選擇。
scott meyers的“有效的現代c ++”第25項,也總結了這一點,沒有給出太多解釋。
Alpha foo() {
Alpha a
return a; // RVO by decent compiler
}
Alpha foo(Alpha a) {
return a; // implicit std::move by compiler
}
Alpha bar(Alpha &&a) {
return a; // Copy ctor due to lvalue
}
Alpha bar(Alpha &&a) {
return std:move(a); // has to be explicit by developer
}
當人們首先了解右值參考時,這是一個非常常見的錯誤。 基本問題是類型和價值類別之間的混淆。
int
是一種類型。 int&
是一種不同的類型。 int&&
是另一種類型。 這些都是不同的類型。
左值和右值是稱為值類別的東西。 請查看這里的精彩圖表: rvalues,lvalues,xvalues,glvalues和prvalues是什么? 。 你可以看到除了左值和右值之外,我們還有prvalues和glvalues和xvalues,它們形成了各種維恩圖的關系。
C ++有一些規則可以說各種類型的變量可以綁定到表達式。 然而,表達式引用類型被丟棄(人們經常說表達式沒有引用類型)。 相反,表達式有一個值類別,它確定哪些變量可以綁定到它。
換句話說:右值引用和左值引用僅與賦值的左側直接相關,即創建/綁定變量。 在右側,我們討論的是表達式而不是變量,而rvalue / lvalue reference-ness僅與確定值類別的上下文相關。
一個非常簡單的例子就是簡單地看一下純粹的int
類型的東西。 int
作為表達式的變量是左值。 但是,由計算返回int
的函數組成的表達式是rvalue。 這對大多數人來說都是直觀的; 關鍵是要分離表達式的類型(甚至在引用被丟棄之前)和它的值類別。
這導致的是,即使int&&
類型的變量只能綁定到rvalues,也不意味着所有類型為int&&
表達式都是 rvalues。 實際上,正如http://en.cppreference.com/w/cpp/language/value_category中的規則所述,任何由命名變量組成的表達式總是一個左值, 無論類型如何 。
這就是為什么你需要std::move
以便將rvalue引用傳遞給rvalue引用的后續函數。 這是因為右值引用不會綁定到其他右值引用。 它們綁定到右值。 如果要獲取移動構造函數,則需要為其綁定一個rvalue,並且命名的rvalue引用不是rvalue。
std::move
是一個返回右值引用的函數。 這種表達的價值范疇是什么? 一個右值? 不。 這是一個xvalue。 這基本上是一個右值,有一些額外的屬性。
在foo
和bar
,表達式a
是左值。 聲明return a;
表示從初始化程序a
初始化返回值對象,並返回該對象。
這兩種情況之間的區別在於,對於這個初始化重載解析取決於是否進行不同地a
聲明為最內層的閉合塊內的非易失性自動對象,或一個函數參數。
它適用於foo
而不是bar
。 (在bar
, a
被聲明為參考)。 所以return a;
在foo
選擇move構造函數來初始化返回值,但return a;
在bar
選擇復制構造函數。
全文是C ++ 14 [class.copy] / 32:
當滿足復制/移動操作的省略標准時,但不滿足異常聲明,並且要復制的對象由左值指定,或者當返回語句中的表達式是(可能帶有括號的)id-時表達式,用於在最內層封閉函數或lambda-expression的body或parameter-declaration-clause中聲明的具有自動存儲持續時間的對象,首先執行重載決策以選擇復制的構造函數,就像對象由rvalue指定一樣。 如果第一個重載決策失敗或未執行,或者所選構造函數的第一個參數的類型不是對象類型的rvalue引用(可能是cv-qualified),則再次執行重載決策,將對象視為左值。 [注意:無論是否發生復制省略,都必須執行此兩階段重載決策。 如果未執行elision,它將確定要調用的構造函數,並且即使調用被省略,也必須可以訪問所選的構造函數。 - 尾注]
其中“符合復制/移動操作的標准”是指[class.copy] /31.1:
- 在具有類返回類型的函數的return語句中,當表達式是具有與函數返回類型相同的cv-unqualified類型的非易失性自動對象(函數或catch子句參數除外)的名稱時,通過將自動對象直接構造到函數的返回值中,可以省略復制/移動操作
注意,這些文本將改變為C ++ 17。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.