右值引用是 C++11 引入的核心特性,徹底解決了歷史遺留的拷貝開銷問題,賦予了 C++ 移動語義與完美轉發能力。本文從左/右值本質、語法規則、核心應用場景、標準庫實現機制、代碼實戰五個維度深度解析,並附帶完整可編譯示例代碼。
| 特性 | 左值 | 右值 |
|---|---|---|
| 定義 | 表達式結束後依然存在的持久對象,有名字、可取地址 | 表達式結束即銷毀的臨時對象,無名字、不可取地址 |
| 典型例子 | 變量、函數返回引用、解引用指針、前置 ++/-- | 字面值、臨時對象、函數返回值、後置 ++/--、算術表達式結果 |
| 引用綁定 | 可綁定到 T&、const T& | 可綁定到 T&&、const T&(延長生命週期) |
核心規則:每個 C++ 表達式要么是左值,要么是右值,無第三種可能。
// 聲明語法:類型後加 &&
int&& rref = 42; // 綁定到右值字面值
int&& rref2 = std::move(x); // 將左值轉為右值引用
// 函數參數
void process(int&& x); // 僅接受右值
void process(const int& x); // 接受左值/右值(但不可修改)
float f=0; int&& i = f; 合法,i 綁定到轉換後的臨時 int。int&& r = foo(); // r 是左值!foo(); // 純右值std::move(r) 將其轉為右值傳遞給移動構造。針對右值(臨時對象)或「即將銷毀的左值」,採用「盜取資源」而非「深拷貝」的方式轉移所有權。
class Buffer {
char* data_;
size_t size_;
public:
// 移動構造:盜取資源,置空源對象
Buffer(Buffer&& other) noexcept
: data_(other.data_), size_(other.size_) {
other.data_ = nullptr;
other.size_ = 0;
}
// 移動賦值
Buffer& operator=(Buffer&& other) noexcept {
if (this != &other) {
delete[] data_;
data_ = other.data_;
size_ = other.size_;
other.data_ = nullptr;
other.size_ = 0;
}
return *this;
}
};
效果:std::vector 擴容、std::string 拼接、std::unique_ptr 轉移所有權等場景性能大幅提升。
結合模板類型推導特殊規則(T&& 為通用引用),配合 std::forward 實現參數原樣轉發給下一層函數,保持左/右值屬性不變。
template
auto wrapper(F&& f, Args&&... args)
-> decltype(std::forward(f)(std::forward(args)...)) {
return std::forward(f)(std::forward(args)...);
}
// 使用
void process(int&); // 左值版
void process(int&&); // 右值版
int x = 10;
wrapper(process, x); // 傳左值 → 調用 process(int&)
wrapper(process, 20); // 傳右值 → 調用 process(int&&)
wrapper(process, std::move(x)); // 轉右值 → 調用 process(int&&)
std::move 本質template
typename std::remove_reference::type&&
move(T&& t) noexcept {
return static_cast::type&&>(t);
}
// 只是無條件轉換為右值引用,不移動任何東西!
std::forward 本質template
T&& forward(typename std::remove_reference::type& t) noexcept {
return static_cast(t);
}
// 僅當 T 為右值引用類型時才轉為右值,實現條件轉發
std::unique_ptr 依賴移動語義禁用拷貝,僅提供移動構造/賦值,確保獨占所有權,編譯期保證無雙重釋放。
std::vector::push_back 重載void push_back(const T& x); // 拷貝插入
void push_back(T&& x); // 移動插入(臨時對象/移動後左值)
自動選擇:傳入臨時對象或 std::move 後的左值 → 調用移動版本,避免拷貝。
#include
#include
#include
#include
// 1. 左值引用測試
void double_string(std::string& s) { s += s; }
char& char_number(std::string& s, std::size_t n) { return s.at(n); }
void test_lvalue_ref() {
std::string s = "Ex";
std::string& r1 = s;
const std::string& r2 = s;
r1 += "ample"; // 修改 s
std::cout << r2 << '\n'; // Example
std::string str = "Test";
double_string(str);
std::cout << str << '\n'; // TestTest
std::string str_ = "Test";
char_number(str_, 1) = 'a'; // 函數調用為左值,可賦值
std::cout << str_ << '\n'; // Tast
}
// 2. 右值引用重載解析
void f(int& x) { std::cout << "lvalue ref: " << x << '\n'; }
void f(const int& x) { std::cout << "const lvalue ref: " << x << '\n'; }
void f(int&& x) { std::cout << "rvalue ref: " << x << '\n'; }
void test_rvalue_ref_overload() {
int i = 1;
const int ci = 2;
f(i); // lvalue ref
f(ci); // const lvalue ref
f(3); // rvalue ref
f(std::move(i)); // rvalue ref
}
// 3. 臨時對象生命週期延長
void test_temp_lifetime() {
std::string s1 = "Test";
// std::string&& r1 = s1; // 錯誤:不能綁定左值
const std::string& r2 = s1 + s1; // const 左值引用延長臨時對象壽命
std::string&& r3 = s1 + s1; // 右值引用同樣延長,且可修改
r3 += "Test";
std::cout << r3 << '\n'; // TestTestTest
}
// 4. 完美轉發實現
template
struct S;
template struct S {
static void print(T& t) { std::cout << "print: " << t << '\n'; }
};
template struct S {
static void print(const T& t) { std::cout << "print: " << t << '\n'; }
};
template struct S {
static void print(T&& t) { std::cout << "print: " << t << '\n'; }
};
template struct S {
static void print(const T&& t) { std::cout << "print: " << t << '\n'; }
};
template
void print_type_and_value(T&& t) {
S::print(std::forward(t));
}
const std::string fourth() { return "fourth"; }
void test_forwarding() {
std::string s1("first");
print_type_and_value(s1); // print
const std::string s2("second");
print_type_and_value(s2); // print
print_type_and_value(std::string("third")); // print
print_type_and_value(fourth()); // print
}
int main() {
test_lvalue_ref();
test_rvalue_ref_overload();
test_temp_lifetime();
test_forwarding();
return 0;
}
noexcept),標準庫容器會自動使用。std::make_unique/shared_ptr 內部均大量使用。std::move:僅在確定對象不再使用時移動,移動後源對象處於「有效但未指定狀態」。T& & → T&, T& && → T&, T&& & → T&, T&& && → T&&,是通用引用工作的基石。