C++的值类别系统是理解现代C++特性的基础。本文将介绍值类别的概念和引用类型
值类别 (Value Categories)
- glvalue(广义左值/generalized lvalue): 有地址的值,包括 lvalue 或 xvalue
- rvalue(右值): 可移动的值,包括 prvalue 和 xvalue
cpp
// 1. 字面量(prvalue)- 天然可移动
42; // 可以移动
"hello"; // 可以移动
// 2. 函数返回值(prvalue)- 天然可移动
int getValue() { return 100; }
getValue(); // 可以移动
// 3. std::move 转换后的值(xvalue)- 强制可移动
std::string str = "hello";
std::move(str); // 可以移动
// 4. 右值引用(xvalue)- 天然可移动
int&& rref = 42;
rref; // 可以移动
// 5. 临时对象(prvalue)- 天然可移动
std::string("temp"); // 可以移动
std::vector<int>{1,2,3}; // 可以移动
- lvalue(左值): 有地址不可移动
cpp
// 变量名
int x = 42;
int& ref = x; // x 是 lvalue
int* ptr = &x; // 可以取地址
// 数组元素
int arr[5];
arr[0] = 10; // arr[0] 是 lvalue
&arr[0]; // 可以取地址
// 解引用指针
int* p = &x;
*p = 20; // *p 是 lvalue
&(*p); // 可以取地址
// 类成员访问
struct Point { int x, y; };
Point pt{1, 2};
pt.x = 3; // pt.x 是 lvalue
&pt.x; // 可以取地址
// 函数调用返回引用
int& getRef() { static int val = 42; return val; }
getRef() = 100; // getRef() 是 lvalue
&getRef(); // 可以取地址
// 字符串字面量(特殊)
"hello"[0] = 'H'; // "hello"[0] 是 lvalue(C++17前)
- xvalue (eXpiring/将亡值): 有地址可移动
cpp
// std::move 转换
int x = 42;
auto moved = std::move(x); // std::move(x) 是 xvalue
&std::move(x); // 可以取地址
// 函数返回右值引用
int&& getRvalueRef() { static int val = 100; return std::move(val); }
auto rref = getRvalueRef(); // getRvalueRef() 返回 xvalue
// 右值引用的成员访问
std::string&& getString() { static std::string s = "hello"; return std::move(s); }
getString().size(); // getString() 是 xvalue,.size() 访问成员
// 数组右值的元素访问
int&& getArray() { static int arr[3] = {1,2,3}; return std::move(arr[0]); }
getArray(); // getArray() 返回 xvalue
// 类成员指针访问右值
struct Data { int value; };
Data&& getData() { static Data d{42}; return std::move(d); }
getData().value; // getData() 是 xvalue,.value 访问成员
// 条件表达式(某些情况)
int a = 1, b = 2;
auto result = (a < b ? std::move(a) : std::move(b)); // 条件表达式是 xvalue
- prvalue (纯右值): 无地址可移动
cpp
// 字面量
42; // 整数字面量是 prvalue
3.14; // 浮点字面量是 prvalue
'x'; // 字符字面量是 prvalue
"hello"; // 字符串字面量是 prvalue
// 函数返回值(非引用)
int getValue() { return 100; }
getValue(); // 函数返回值是 prvalue
// 算术运算结果
int a = 10, b = 20;
a + b; // 算术表达式结果是 prvalue
a * b; // 乘法表达式结果是 prvalue
// 比较运算结果
a == b; // 比较表达式结果是 prvalue
a < b; // 比较表达式结果是 prvalue
// 逻辑运算结果
a && b; // 逻辑表达式结果是 prvalue
!a; // 逻辑非表达式结果是 prvalue
// 类型转换
static_cast<int>(3.14); // 类型转换结果是 prvalue
(int)3.14; // C风格转换结果是 prvalue
// 临时对象构造
std::string("hello"); // 临时对象是 prvalue
std::vector<int>{1,2,3}; // 临时对象是 prvalue
// lambda 表达式
[]{ return 42; }; // lambda 表达式是 prvalue
// prvalue 无地址(不能取地址)
// &42; // 错误:不能取字面量地址
// &getValue(); // 错误:不能取函数返回值地址
// &(a + b); // 错误:不能取算术表达式地址
// prvalue 可移动
void h(int&&);
h(42); // 字面量可以绑定到右值引用
h(getValue()); // 函数返回值可以绑定到右值引用
h(a + b); // 算术表达式结果可以绑定到右值引用
值类别检测
cpp
#include <type_traits>
#include <utility>
template<typename T>
void check_value_category(const char* name, T&& expr) {
std::cout << name << ":\n";
std::cout << " is_lvalue: " << std::is_lvalue_reference_v<decltype((expr))> << "\n";
std::cout << " is_xvalue: " << std::is_rvalue_reference_v<decltype((expr))> << "\n";
std::cout << " is_prvalue: " << !std::is_reference_v<decltype((expr))> << "\n";
}
int main() {
int x = 42;
check_value_category("x", x); // lvalue
check_value_category("42", 42); // prvalue
check_value_category("std::move(x)", std::move(x)); // xvalue
}
引用类型
左值引用 (Lvalue Reference)
cpp
int x = 42;
int& ref = x; // 左值引用
ref = 100; // 修改原值
// 数组元素 lvalue
int arr[5] = {1, 2, 3, 4, 5};
int& arr_ref = arr[0]; // 绑定到数组元素 lvalue
// 解引用指针 lvalue
int* ptr = &x;
int& ptr_ref = *ptr; // 绑定到解引用 lvalue
// 类成员 lvalue
struct Point { int x, y; };
Point pt{1, 2};
int& member_ref = pt.x; // 绑定到成员 lvalue
// 函数返回引用 lvalue
int& getRef() { static int val = 42; return val; }
int& func_ref = getRef(); // 绑定到函数返回的 lvalue
特点:
- 只能绑定到 lvalue
- 可以修改被引用的对象
- 生命周期与引用对象相同
右值引用 (Rvalue Reference)
cpp
// 绑定到 prvalue(字面量)
int&& rref1 = 42; // 绑定到整数字面量 prvalue
std::string&& str_ref = "hello"; // 绑定到字符串字面量 prvalue
// 绑定到 prvalue(函数返回值)
int getValue() { return 100; }
int&& rref2 = getValue(); // 绑定到函数返回值 prvalue
// 绑定到 prvalue(算术表达式)
int a = 10, b = 20;
int&& rref3 = a + b; // 绑定到算术表达式结果 prvalue
// 绑定到 xvalue(std::move 转换)
std::string str = "world";
std::string&& rref4 = std::move(str); // 绑定到 xvalue
// 绑定到 xvalue(函数返回右值引用)
int&& getRvalueRef() { static int val = 200; return std::move(val); }
int&& rref5 = getRvalueRef(); // 绑定到函数返回的 xvalue
// 绑定到 xvalue(临时对象)
std::vector<int>&& vec_ref = std::vector<int>{1, 2, 3}; // 绑定到临时对象 prvalue
特点:
- 只能绑定到 rvalue(prvalue 或 xvalue)
- 用于移动语义
- 延长临时对象生命周期
常量左值引用 (Const Lvalue Reference)
cpp
// 绑定到 lvalue
int x = 42;
const int& cref1 = x; // 绑定到 lvalue
// 绑定到 prvalue(字面量)
const int& cref2 = 42; // 绑定到整数字面量 prvalue
const std::string& str_cref = "hello"; // 绑定到字符串字面量 prvalue
// 绑定到 prvalue(函数返回值)
int getValue() { return 100; }
const int& cref3 = getValue(); // 绑定到函数返回值 prvalue
// 绑定到 prvalue(算术表达式)
int a = 10, b = 20;
const int& cref4 = a + b; // 绑定到算术表达式结果 prvalue
// 绑定到 xvalue
std::string str = "world";
const std::string& cref5 = std::move(str); // 绑定到 xvalue
// 绑定到临时对象 prvalue
const std::vector<int>& vec_cref = std::vector<int>{1, 2, 3};
特点:
- 可以绑定任何值(lvalue、prvalue、xvalue)
- 不能修改被引用的对象
- 常用于函数参数,避免拷贝
移动语义 (Move Semantics)
移动语义是C++11引入的重要特性,通过避免不必要的拷贝来提高性能。
移动构造函数和移动赋值运算符
cpp
class MyClass {
public:
// 移动构造函数
MyClass(MyClass&& other) noexcept
: data_(std::move(other.data_)) {
other.data_ = nullptr;
}
// 移动赋值运算符
MyClass& operator=(MyClass&& other) noexcept {
if (this != &other) {
delete data_;
data_ = std::move(other.data_);
other.data_ = nullptr;
}
return *this;
}
private:
int* data_;
};
std::move 和 std::forward
- std::move 将左值改为右值
- std::forward 会根据传入参数的值类别,决定转发为左值引用还是右值引用
cpp
#include <utility>
template<typename T>
void process(T&& arg) {
// std::move: 无条件转换为右值
auto moved = std::move(arg);
// std::forward: 保持值类别
auto forwarded = std::forward<T>(arg);
}