C++拷贝消除 Copy Elision

拷贝消除

在广义上,指满足一定条件时,编译期允许省略对临时对象的拷贝或移动操作。在C++17之前,对于这一特性都是“允许但不要求”,但绝大多数主流的编译器都会启用。从C++17开始,只要满足条件,对prvalue的拷贝/移动省略都是必选的。

一般来说,拷贝消除包含

  • 返回值优化 RVO(Return Value Optimization)
  • 具名返回值优化(Named Return Value Optimization)

返回值优化 RVO(Return Value Optimization)

函数直接返回一个匿名临时对象时,编译器可以将这个临时对象直接构造在调用者的目标空间中,省掉拷贝。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <iostream>
class RVOTest {
public:
RVOTest() {
std::cout << "默认构造" << std::endl;
}
RVOTest(const RVOTest & other) {
std::cout << "拷贝构造" << std::endl;
}

~RVOTest() {
std::cout << "析构" << std::endl;
}
};

RVOTest get() {
return RVOTest();
}
int main() {
// RVOTest a = RVOTest(); //如果拷贝构造函数的参数没有const的话,error: 类 "RVOTest" 没有适当的复制构造函数 (这里的RVOTest()是个临时对象是个右值,而const左值引用也可以绑定右值)
RVOTest b = get();
return 0;
}

运行上面的代码,会输出

1
2
默认构造
析构

也就是说,当用get()返回的RVOTest对象来初始化b时,并没有调用拷贝构造函数用临时对象RVOTest()来初始化b,而是直接在调用者,也就是b的存储区就地构造。

具名返回值优化(Named Return Value Optimization)

当返回的对象有名字(即局部变量)时,也可能进行省略拷贝。

1
2
3
4
5
6
7
8
RVOTest getNamed() {
RVOTest obj;
return obj;
}
int main() {
RVOTest b = getNamed();
return 0;
}

运行上面的代码,会输出

1
2
默认构造
析构

与RVO不同,NRVO的触发条件更加严苛,例如下面的情况就不会触发NRVO

1. 返回不同分支的不同命名局部对象

在某些编译器里可能无法决定统一的内存位置,从而放弃 NRVO。

1
2
3
4
5
6
7
8
9
RVOTest flag(bool flag) {
RVOTest a, b;
if (flag) return a;
else return b;
}
int main() {
RVOTest c = flag(false);
return 0;
}

运行上面的代码,会输出

1
2
3
4
5
6
7
默认构造
默认构造
~~~~~~
拷贝构造
析构
析构
析构

2. 返回函数参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
RVOTest rParam(RVOTest param) { //这里也会调用一次拷贝构造函数
std::cout << "~~~~~~" << std::endl;
return param; // param 不是本地新建对象,无法 NRVO
}
RVOTest rParamR(RVOTest& param) {
std::cout << "~~~~~~" << std::endl;
return param; // param 不是本地新建对象,无法 NRVO
}
int main() {
RVOTest b = get();
RVOTest d = rParam(b);
std::cout << "======" << std::endl;
RVOTest e = rParamR(b);
return 0;
}

运行上面的代码,会输出

1
2
3
4
5
6
7
8
9
10
11
默认构造
拷贝构造
~~~~~~
拷贝构造
析构
======
~~~~~~
拷贝构造
析构
析构
析构

3. 复杂表达式

返回复杂表达式,例如某个函数调用、三目表达式等,也可能不会触发NRVO

1
2
3
4
5
6
7
8
9
RVOTest tri() {
RVOTest a, b;
return 1 > 2 ? a : b;
}

int main() {
RVOTest f = tri();
return 0;
}

运行上面的代码,会输出

1
2
3
4
5
6
默认构造
默认构造
拷贝构造
析构
析构
析构

C++拷贝消除 Copy Elision
https://guts.homes/2025/06/17/cpp-RVO/
作者
guts
发布于
2025年6月17日
许可协议