C++11 標準引入右值引用(Rvalue Reference)主要為了解決兩個核心問題:
在 C++ 中,所有表達式非左值即右值:
int i = 0; 中的 i)。0、臨時物件 MyString("Hello"))。C++11 前只能用 const T& 綁定右值,但右值實際上可修改(如 T().set().get()),因此引入 T&& 表示右值引用。
void process_value(int& i) { std::cout << "LValue processed: " << i << std::endl; }
void process_value(int&& i) { std::cout << "RValue processed: " << i << std::endl; }
int main() {
int a = 0;
process_value(a); // 輸出 LValue processed: 0
process_value(1); // 輸出 RValue processed: 1
}
關鍵點:命名的右值引用參數在函式內部是左值。若直接傳遞給另一個函式,會觸發左值重載:
void forward_value(int&& i) { process_value(i); } // i 這裡是左值
// 呼叫 forward_value(2) 會印出 "LValue processed: 2"
移動語義類比「剪貼」取代「複製貼上」,將資源(堆記憶體、系統控制代碼等)從臨時物件「偷移」到目標物件,避免重複分配/釋放。
需定義移動建構函式與移動賦值運算子,參數為非 const 右值引用:
class MyString {
char* _data; size_t _len;
public:
// 移動建構函式
MyString(MyString&& str) noexcept {
_len = str._len; _data = str._data;
str._len = 0; str._data = nullptr; // 關鍵:掏空來源
}
// 移動賦值運算子
MyString& operator=(MyString&& str) noexcept {
if (this != &str) {
delete[] _data;
_len = str._len; _data = str._data;
str._len = 0; str._data = nullptr;
}
return *this;
}
// ... 拷貝版本省略 ...
};
加上移動操作後,a = MyString("Hello") 呼叫移動賦值,vec.push_back(MyString("World")) 呼叫移動建構,輸出:
Move Assignment is called! source: Hello
Move Constructor is called! source: World
當確定某具名物件不再使用時,可用 std::move 強制啟用移動語義:
int a = 0;
ProcessValue(a); // LValue
ProcessValue(std::move(a)); // RValue
經典應用:優化 swap,從 3 次拷貝降為 3 次移動:
template <class T> void swap(T& a, T& b) {
T tmp(std::move(a));
a = std::move(b);
b = std::move(tmp);
}
場景:泛型函式需將參數原封不動轉發給另一函式(含左值/右值、const/non-const)。
傳統做法需重載 const T& 與 T& 兩版本,參數多時組合爆炸。C++11 利用通用引用(T&& 在模板推導中)單一版本即可:
template <typename T>
void forward_value(T&& val) { process_value(val); }
// 推導規則:右值實參 → 右值引用;左值實參 → 左值引用
int a = 0; const int& b = 1;
forward_value(a); // T=int&, val 為 int& (左值)
forward_value(b); // T=const int&, val 為 const int&
forward_value(2); // T=int, val 為 int&& (右值)
配合 std::forward<T>(val) 即可完美轉發。
noexcept 以利標準庫優化。std::move 表達所有權轉移意圖。右值引用與移動語義是現代 C++ 效能與表達力的基石,掌握它們能寫出更快、更清晰的程式碼。