C++ 左值引用與右值引用詳解
在 C++ 的類型系統中,引用(reference) 是一種為已有對象起別名的機制。在早期(C++98/03)中,C++ 只有 左值引用(lvalue reference),主要用于函數參數傳遞、返回值以及避免拷貝。到了 C++11,引入了 右值引用(rvalue reference),為完美轉發(perfect forwarding)和移動語義(move semantics)提供了語言支持,大大提升了性能和靈活性。
一、左值與右值的基本概念
在解釋左值引用和右值引用之前,需要弄清楚 左值(lvalue) 和 右值(rvalue) 的含義。
1. 左值(lvalue)
- 左值表示在表達式結束后仍然存在的對象,可以取地址,有名字。
- 特點:可以出現在賦值號的左邊(其實是因為可尋址,而不是單純因為在左邊)。
- 常見例子:
int x = 10; // x 是一個左值
x = 20; // 左值可以出現在賦值號的左邊
&x; // 可以取地址
2. 右值(rvalue)
- 右值表示表達式結束后不再存在的臨時對象,通常不能取地址,沒有名字。
- 特點:只能出現在賦值號的右邊(通常是字面量、表達式結果等)。
- 常見例子:
10; // 字面量是右值
x + 5; // 表達式結果是右值
std::string("Hello"); // 臨時對象是右值
二、左值引用(lvalue reference)
1. 定義
左值引用的語法形式是在類型名后面加上 &
:
int a = 10;
int& ref = a; // ref 是 int 類型的左值引用,綁定到 a
此時 ref
就是 a
的別名,對 ref
的修改就是對 a
的修改。
2. 特點
- 只能綁定到左值。
- 一旦綁定,引用不可更換綁定對象。
- 常用于函數參數傳遞,避免拷貝,提高性能。
3. 示例
void increment(int& n) {n++;
}int main() {int x = 5;increment(x); // x 被修改為 6
}
三、右值引用(rvalue reference)
1. 定義
右值引用的語法形式是在類型名后面加上 &&
:
int&& rref = 10; // rref 綁定到右值 10
右值引用可以綁定到臨時對象(右值),使得我們可以在它被銷毀之前對其進行修改或“移動”。
2. 特點
- 只能綁定到右值(包括字面量、臨時對象、表達式結果等)。
- 常用于移動語義和完美轉發。
- 避免不必要的深拷貝,提高性能。
3. 示例
#include <iostream>
#include <vector>void printVector(std::vector<int>&& v) {std::cout << "Size: " << v.size() << '\n';
}int main() {std::vector<int> tmp = {1, 2, 3};printVector(std::move(tmp)); // 將 tmp 轉換為右值引用
}
四、移動語義與右值引用
在 C++98/03 中,如果我們返回一個大對象,會產生不必要的深拷貝:
std::string getStr() {std::string s = "Hello";return s; // 舊標準中可能拷貝一次
}
C++11 引入了 移動構造函數 和 右值引用,允許“竊取”臨時對象的資源,而不是復制。
移動構造函數示例
#include <iostream>
#include <string>class MyString {char* data;
public:MyString(const char* s) {data = new char[strlen(s)+1];strcpy(data, s);}// 移動構造函數MyString(MyString&& other) noexcept {data = other.data;other.data = nullptr;std::cout << "Moved!\n";}~MyString() { delete[] data; }
};int main() {MyString a("Hello");MyString b(std::move(a)); // 調用移動構造
}
五、完美轉發與 std::forward
當我們寫一個模板函數時,可能希望保留實參的值類別(左值或右值)。使用 右值引用 + 引用折疊規則 + std::forward
可以實現完美轉發。
#include <utility>
#include <iostream>void process(int& x) { std::cout << "Lvalue\n"; }
void process(int&& x) { std::cout << "Rvalue\n"; }template<typename T>
void forwarder(T&& arg) {process(std::forward<T>(arg));
}int main() {int a = 5;forwarder(a); // 輸出 Lvalueforwarder(10); // 輸出 Rvalue
}
六、引用折疊規則
C++ 的引用折疊規則規定:
T& &
折疊為T&
T& &&
折疊為T&
T&& &
折疊為T&
T&& &&
折疊為T&&
這使得 萬能引用(universal reference)(或者叫轉發引用 forwarding reference)成為可能。
七、注意事項
- 右值引用不是萬能的:綁定到右值意味著你可以修改它,但并不是強制要移動它。
- 使用
std::move
:std::move
并不移動對象,只是將左值強制轉換為右值引用。 - 警惕懸掛引用:右值引用綁定到臨時對象,如果臨時對象銷毀,引用就懸空。
- 移動后對象可用但狀態不確定:移動語義通常會清空被移動對象的內部資源,但允許它被安全析構。
八、總結對比表
特性 | 左值引用 (T&) | 右值引用 (T&&) |
---|---|---|
可綁定對象類型 | 左值 | 右值 |
常見用途 | 參數傳遞,避免拷貝 | 移動語義,完美轉發 |
C++ 引入版本 | C++98 | C++11 |
是否可更換綁定對象 | 否 | 否 |
是否支持取地址 | 是 | 是 |