我試圖通過一個簡單的自制示例來掌握右值參考和移動語意,但我無法理解特定部分。我創建了以下類:
class A {
public:
A(int a) {
cout << "Def constructor" << endl;
}
A(const A& var) {
cout << "Copy constructor" << endl;
}
A(A&& var) {
cout << "Move constructor" << endl;
}
A& operator=(const A& var) {
cout << "Copy Assignment" << endl;
return *this;
}
A& operator=(A&& var) {
cout << "Move Assignment" << endl;
return *this;
}
};
我嘗試了以下實驗,看看我是否可以預測建構式/運算子將如何被呼叫:
- A a1(1) - 將呼叫默認建構式。 預測。
- A a2 = a1 - 將呼叫復制建構式。預測。
- a1 = a2 - 將呼叫復制賦值運算子。 預測。
現在,我創建了一個簡單的函式,它只回傳一個 A 物件。
A helper() {
return A(1);
}
- A a3 = helper() - 將呼叫默認建構式以創建助手回傳的物件。由于 RVO,將不會呼叫移動建構式。預測。
- a3 = helper() - 將呼叫默認建構式以創建助手回傳的物件。然后,將呼叫移動賦值運算子。預測。
現在到了我不明白的部分。我創建了另一個完全沒有意義的函式。它按值接受一個 A 物件,它只是回傳它。
A helper_alt(A a) {
return a;
}
- A a4 = helper_alt(a1) - 這將呼叫復制建構式,以實際復制函式中的物件 a1,然后是移動建構式。預測。
- a4 = helper_alt(a1) - 這將呼叫復制建構式,以實際復制函式中的物件 a1 然后我認為移動賦值運算子將被呼叫但是正如我所見,首先,移動建構式被呼叫并且然后呼叫移動賦值運算子。沒有想法。
拜托,如果我說的有任何錯誤,或者你覺得我可能沒有理解某些東西,請隨時糾正我。
我的實際問題:在最后一種情況下,為什么先呼叫移動建構式,然后呼叫移動賦值運算子,而不僅僅是移動賦值運算子?
uj5u.com熱心網友回復:
恭喜,你發現了 C 的一個核心問題!
關于您在示例代碼中看到的行為,仍有很多討論。
有這樣的建議:
A&& helper_alt(A a) {
std::cout << ".." << std::endl;
return std::move(a);
}
這將執行您想要的操作,只需使用移動賦值,但會發出來自 g 的警告“警告:對區域變數 'a' 的參考回傳”,即使該變數立即超出范圍。
已經其他人發現了這個問題,這已經成為c 標準語言的核心問題
有趣的是,這個問題早在 2010 年就已經發現了,但直到現在才解決……
為了回答您的問題“在最后一種情況下,為什么要呼叫移動建構式,然后呼叫移動賦值運算子,而不僅僅是移動賦值運算子? ”是,C 委員會直到現在也沒有答案. 確切地說,有一個提議的解決方案,這個解決方案被接受了,但直到現在還不是語言的一部分。
來自:評論狀態
修改第 34 段以明確從復制省略中排除函式引數。修改第 35 段以包括符合移動構造條件的函式引數。
uj5u.com熱心網友回復:
考慮下面的例子。我已經使用 -fno-elide-constructors 標志編譯了示例代碼以防止 RVO 優化:
g -fno-elide-constructors -o test test.cpp
#include<iostream>
using namespace std;
class A {
public:
A(int a) {
cout << "Def constructor" << endl;
}
A(const A& var) {
cout << "Copy constructor" << endl;
}
A(A&& var) {
cout << "Move constructor" << endl;
}
A& operator=(const A& var) {
cout << "Copy Assignment" << endl;
return *this;
}
A& operator=(A&& var) {
cout << "Move Assignment" << endl;
return *this;
}
};
A a_global(1);
A helper_alt(A a) {
return a;
}
A helper_a_local(A a) {
A x(1);
return x;
}
A helper_a_global(A a) {
return a_global;
}
int main(){
A a1(1);
A a4(4);
std::cout << "================= helper_alt(a1) ==================" << std::endl;
a4 = helper_alt(a1);
std::cout << "=============== helper_a_local() ================" << std::endl;
a4 = helper_a_local(a1);
std::cout << "=============== helper_a_global() ================" << std::endl;
a4 = helper_a_global(a1);
return 0;
}
這將導致以下輸出:
Def constructor
Def constructor
Def constructor
================= helper_alt(a1) ==================
Copy constructor
Move constructor
Move Assignment
=============== helper_a_local() ================
Copy constructor
Def constructor
Move constructor
Move Assignment
=============== helper_a_global() ================
Copy constructor
Copy constructor
Move Assignment
可以看出,簡單來說,rvalue當回傳型別不是參考時,C 會構造一個新的臨時物件(),這導致根據回傳物件的值類別呼叫Move或Copy建構式。
此外,似乎函式的所有區域變數(包括按值傳遞的引數)都有一個value category(例如,xvalue)可以系結到rvalue references,似乎就是這種情況。我會感謝任何可以value category在函式內部提供有關函式引數(按值傳遞)的參考的人。
無論如何,我認為呼叫建構式背后的邏輯是您沒有使用參考,并且應該首先通過復制或移動建構式來解釋回傳的身份,具體取決于回傳的value category(考慮存在兩個回傳運算式的情況,一個回傳一個臨時物件,另一個回傳一個lvalue)。最后,RVO通過優化代碼來減少不必要的移動和復制,這甚至可以為基本程式員帶來優化的二進制檔案!
轉載請註明出處,本文鏈接:https://www.uj5u.com/shujuku/385115.html
