C++左值右值
左值指有地址可取的对象,右值指临时值或字面量,右值引用支持资源移动优化。
C++左值右值
C++ 左值右值
- 左值(lvalue):有名字、有地址的对象,能出现在赋值号左边
- 右值(rvalue):没有名字、临时存在的值,只能出现在赋值号右边
如何判断
能否被赋值? | 能否取地址? | 类型 |
---|---|---|
能 | 能 | 左值 |
不能 | 不能(大部分情况) | 右值 |
1
2
3
int x = 42;
&x; // 左值能取地址
&(x + 1); // 编译错误,右值不能取地址
常见示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
int a = 10; // a 是左值(有名字,可以 &a 取地址)
// 10 是右值(字面量常量,不能取地址)
int b = a + 5; // b 是左值(变量,有名字,可以取地址)
// a + 5 是右值(表达式结果的临时值)
// ------------------- 一些容易混淆的情况 -------------------
int c = (a); // (a) 仍然是左值(加括号不影响)
int d = a++; // a++ 返回右值(自增前的临时值)
// a 本身依旧是左值
int e = ++a; // ++a 返回左值(自增后的 a,本身可以继续取地址)
int f = a * 2; // a * 2 是右值(临时结果)
// f 是左值(变量)
int& foo(); // 假设 foo 返回 int& (引用)
int bar(); // 假设 bar 返回 int (值)
int& r = foo(); // foo() 返回左值(引用,可取地址)
int x = bar(); // bar() 返回右值(纯值,临时对象)
示例 | 类型 | 说明 |
---|---|---|
变量 x | 左值 | 有地址、可以赋值 |
字面量 10 | 右值 | 没地址、不能取地址 |
x + 1 | 右值 | 运算结果是一个临时值 |
"abc" | 右值 | 字符串字面量是临时值 |
函数返回值(返回非引用) | 右值 | |
函数返回引用 | 左值 |
与引用的关系
引用类型 | 能绑定右值 | 能绑定左值 | 用途 |
---|---|---|---|
T& | 不能 | 能 | 修改已有变量 |
const T& | 能 | 能 | 安全地读取,不可修改 |
T&& | 能 | 不能 | 专门操作右值,进行移动 |
1
2
3
4
5
6
int a = 10;
int& r1 = a; // 左值引用绑定左值
int& r2 = 10; // 错误,不能绑定右值
const int& r3 = 10; // 常量引用可以绑定右值
int&& r4 = 10; // 右值引用绑定右值
右值引用
语法是 T&&
,只允许绑定右值(不能绑定有名字的变量)。右值引用让你可以“接住”临时对象的资源,然后偷走它们,而不是复制。
移动语义
1
2
3
4
5
6
7
std::string a = "hello";
std::string b = std::move(a); // move 后,a 的资源转移给 b
std::string s1 = "abc";
std::string s2 = s1; // 拷贝构造(复制内容)
std::string s3 = std::move(s1); // 移动构造(直接窃取 s1 的资源)
移动构造函数/赋值运算符
1
2
3
4
5
6
7
8
9
10
11
class MyClass {
public:
MyClass(MyClass&& other) noexcept {
// 把 other 的资源“偷”过来
}
MyClass& operator=(MyClass&& other) noexcept {
// 先释放自己资源,再接管 other's
return *this;
}
};
强烈推荐给移动构造函数加上 noexcept
,因为标准库容器(如 std::vector
、std::string
) 在内部做扩容时:
- 优先使用移动构造函数(比拷贝效率高)
- 但前提是:这个移动构造是
noexcept
的
否则就退回使用拷贝构造,导致性能变差。
引用限定符
引用限定符(Ref-qualifiers)是 C++11 引入的一种语法,用于区分成员函数对对象引用类型的限定,常见于成员函数声明后面,表示该成员函数只能被左值对象或右值对象调用。
主要用来控制成员函数的调用权限,尤其对重载成员函数行为和资源管理(比如移动语义)非常重要。
- 左值引用限定符
&
:表示该成员函数只能被左值对象调用。 - 右值引用限定符
&&
:表示该成员函数只能被右值对象调用。 - 无引用限定符:该成员函数可以被左值和右值调用。
1
2
3
4
5
6
7
8
9
10
11
struct Foo {
void foo() & { std::cout << "called on lvalue\n"; }
void foo() && { std::cout << "called on rvalue\n"; }
};
int main() {
Foo f;
f.foo(); // 调用左值版本
Foo().foo(); // 调用右值版本
}
输出:
1
2
called on lvalue
called on rvalue
用途详解
区分对左值和右值的操作
允许对左值对象和右值对象提供不同的行为,尤其对支持移动语义的类非常重要。
当操作临时对象(右值)时,可以安全地“偷走”资源(移动构造或移动赋值)。
当操作具名对象(左值)时,通常不希望破坏原对象。
防止错误的调用
比如写一个函数只想在临时对象上调用,或者只允许在持久对象上调用,引用限定符可以做到。
配合移动语义
比如
std::string
中的operator+
,返回的临时字符串可以调用带&&
限定符的成员函数,避免不必要的复制。
结合 const 使用
引用限定符可以和const
一起用:
1
2
void foo() const &; // const左值对象调用
void foo() const &&; // const右值对象调用
函数签名 | 调用对象类型限制 | 备注 |
---|---|---|
void f() | 左值和右值均可 | 默认 |
void f() & | 只能左值调用 | 用于修改左值的成员函数 |
void f() && | 只能右值调用 | 用于移动语义或优化 |
void f() const & | 只能常量左值调用 | 不修改对象的左值 |
void f() const && | 只能常量右值调用 | 不修改对象的右值 |
只用于非静态成员函数声明末尾,表示该成员函数只能被特定类型的对象调用。
如果一个成员函数写了引用限定符(比如
&
或&&
),那么所有该函数同名、参数列表完全相同的重载版本,也都建议写上引用限定符。1 2 3 4 5 6
struct A { void foo() & { std::cout << "lvalue\n"; } // void foo() && { std::cout << "rvalue\n"; } // 如果这里没写 foo() &&,对右值调用 foo() 会找不到匹配版本 // 要么都写上引用限定符,覆盖所有情况;要么都不写,保持传统行为 };
引用限定符是函数签名的一部分,
foo() &
和foo()
是两种不同的成员函数签名。
本文由作者按照 CC BY 4.0 进行授权