返回值优化
引言
C++11的返回值优化 (Return Value Optimization, RVO)是拷贝消除 (Copy Elision)优化技术的一种重要实现,其核心目标是在编译阶段消除函数返回过程中不必要的对象拷贝和临时对象构造,从而提升程序性能。
在C++11之前,RVO是一种编译器可选的优化。C++11标准正式将包括RVO在内的拷贝消除纳入了语言规范
返回值优化原理
当返回值大小大于8字节时,会提前开辟一块空间。
以下是函数栈图
高地址
+------------------+
| 调用者栈帧 |
| - 局部变量 |
| - 返回地址 |
| - 旧栈帧指针 |
+------------------+
| 返回值空间 | ← 为返回值预留的空间
+------------------+
| 被调用函数栈帧 |
| - 参数 |
| - 局部变量 |
| - 临时空间 |
低地址
未开启优化时,在被调用函数中创建局部变量。返回时,将局部变量移动到临时空间,局部变量析构。
开启优化时,直接在临时空间中进行创建。
返回值优化形式
返回值优化主要有两种形式:
-
RVO (Return Value Optimization) :优化返回无名临时对象(纯右值)。
MyObject createObject() { return MyObject(); } -
NRVO (Named Return Value Optimization) :优化返回命名的局部对象。
MyObject createObject(int val) { MyObject obj(val); // 命名的局部对象 // ... 一些操作 return obj; // 返回命名对象 }
示例01
cpp
#include <iostream>
class Data {
public:
int* buffer;
size_t size;
// 构造函数
Data(size_t n) : size(n) {
buffer = new int[n];
std::cout << "构造函数: 分配 " << n << " 个int的内存 @" << buffer << " this:" << this << std::endl;
}
// 拷贝构造函数
Data(const Data& other) : size(other.size) {
buffer = new int[size];
std::copy(other.buffer, other.buffer + size, buffer);
std::cout << "拷贝构造: 从 @" << other.buffer << " 拷贝到 @" << buffer << " this:" << this << std::endl;
}
// 移动构造函数
Data(Data&& other) noexcept : buffer(other.buffer), size(other.size) {
other.buffer = nullptr;
other.size = 0;
std::cout << "移动构造: 转移 @" << buffer << " 的所有权 this:" << this << " from:" << &other << std::endl;
}
// 析构函数
~Data() {
if (buffer) {
std::cout << "析构函数: 释放 @" << buffer << " this:" << this << std::endl;
delete[] buffer;
} else {
std::cout << "析构函数: 空指针 this:" << this << std::endl;
}
}
};
// NRVO版本:返回命名局部对象
Data createDataNRVO(size_t n) {
Data localData(n);
std::cout << "NRVO函数中 localData地址: " << &localData << std::endl;
localData.buffer[0] = 100;
// some logic...
return localData;
}
int main() {
std::cout << "=== main开始 ===" << std::endl;
Data myData = createDataNRVO(1000); // 调用函数
std::cout << "myData地址: " << &myData << std::endl;
std::cout << "myData.buffer地址: " << myData.buffer << std::endl;
std::cout << "=== main结束 ===" << std::endl;
return 0;
}
未开启返回值优化
output
=== main开始 ===
构造函数: 分配 1000 个int的内存 @0x12a5a0 this:0x5ffe10
NRVO函数中 localData地址: 0x5ffe10
移动构造: 转移 @0x12a5a0 的所有权 this:0x5ffe60 from:0x5ffe10
析构函数: 空指针 this:0x5ffe10
myData地址: 0x5ffe60
myData.buffer地址: 0x12a5a0
=== main结束 ===
析构函数: 释放 @0x12a5a0 this:0x5ffe60
开启返回值优化
output
=== main开始 ===
构造函数: 分配 1000 个int的内存 @0x7fa5a0 this:0x5ffe60
NRVO函数中 localData地址: 0x5ffe60
myData地址: 0x5ffe60
myData.buffer地址: 0x7fa5a0
=== main结束 ===
析构函数: 释放 @0x7fa5a0 this:0x5ffe60
示例02
cpp
#include <iostream>
class Data {
public:
int* buffer;
size_t size;
// 构造函数
Data(size_t n) : size(n) {
buffer = new int[n];
std::cout << "构造函数: 分配 " << n << " 个int的内存 @" << buffer << " this:" << this << std::endl;
}
// 拷贝构造函数
Data(const Data& other) : size(other.size) {
buffer = new int[size];
std::copy(other.buffer, other.buffer + size, buffer);
std::cout << "拷贝构造: 从 @" << other.buffer << " 拷贝到 @" << buffer << " this:" << this << std::endl;
}
// 移动构造函数
Data(Data&& other) noexcept : buffer(other.buffer), size(other.size) {
other.buffer = nullptr;
other.size = 0;
std::cout << "移动构造: 转移 @" << buffer << " 的所有权 this:" << this << " from:" << &other << std::endl;
}
// 析构函数
~Data() {
if (buffer) {
std::cout << "析构函数: 释放 @" << buffer << " this:" << this << std::endl;
delete[] buffer;
} else {
std::cout << "析构函数: 空指针 this:" << this << std::endl;
}
}
};
// NRVO版本:返回命名局部对象
Data createDataNRVO(size_t n) {
Data localData(n);
std::cout << "NRVO函数中 localData地址: " << &localData << std::endl;
localData.buffer[0] = 100;
// some logic...
return localData;
}
// RVO版本:返回匿名临时对象
Data createDataRVO(size_t n) {
std::cout << "RVO函数中直接返回临时对象" << std::endl;
return Data(n);
}
// 混合版本:有时返回命名对象,有时返回临时对象
Data createDataMixed(bool useTemp, size_t n) {
if (useTemp) {
std::cout << "Mixed函数返回临时对象" << std::endl;
return Data(n); // RVO场景, C++17强制触发
} else {
Data localData(n);
std::cout << "Mixed函数返回命名对象 localData地址: " << &localData << std::endl;
return localData; // NRVO场景,可能不会触发
}
}
int main() {
std::cout << "=== 测试NRVO(命名返回值优化)===" << std::endl;
{
Data myData1 = createDataNRVO(1000);
std::cout << "main中 myData1地址: " << &myData1 << std::endl;
std::cout << "myData1.buffer地址: " << myData1.buffer << std::endl;
}
std::cout << "\n=== 测试RVO(返回值优化)===" << std::endl;
{
Data myData2 = createDataRVO(2000);
std::cout << "main中 myData2地址: " << &myData2 << std::endl;
std::cout << "myData2.buffer地址: " << myData2.buffer << std::endl;
}
std::cout << "\n=== 测试Mixed(混合情况)===" << std::endl;
{
Data myData3 = createDataMixed(true, 3000);
std::cout << "main中 myData3地址: " << &myData3 << std::endl;
std::cout << "myData3.buffer地址: " << myData3.buffer << std::endl;
}
std::cout << "\n=== 测试Mixed(命名对象情况)===" << std::endl;
{
Data myData4 = createDataMixed(false, 4000);
std::cout << "main中 myData4地址: " << &myData4 << std::endl;
std::cout << "myData4.buffer地址: " << myData4.buffer << std::endl;
}
std::cout << "\n=== main结束 ===" << std::endl;
return 0;
}
Note
不会触发返回值优化(RVO/NRVO)的常见情况:
- 返回路径不统一
- 返回非局部对象
- 编译器无法确定返回位置
- 编译设置限制