返回值优化
在以下几种情况中,编译器可能会省略对象的拷贝和移动操作。对象直接在原本拷贝/移动的内存中直接构造对象。当发生这种优化时,虽然拷贝/移动构造函数没有调用,但是拷贝/移动构造函数必须是可访问的,否则程序是错误的。
- 在
return
语句中,当操作数是拥有自动存储期,命名的对象时,且该操作数不是函数参数和catch
语句的参数,并且该操作数的类型与函数的返回类型一致。这种优化被称为NRVO(named return value optimization)
。 - 在对象的初始化中,当源对象是一个未命名的临时量,且该临时量与目标对象拥有相同的类型。当该未命名的临时量作为
return
语句的操作数时,这种优化被称为RVO(return value optimization)
。 - 在
throw
表达式中,当操作数是一个拥有自动存储期,非volatile
的对象,且该操作数不是函数的参数,不是catch
语句的参数,并且这个该操作数的范围不超过最内部的try
块。 - 在
catch
语句中,当参数与抛出的异常对象类型一致时,异常对象的拷贝将会被省略,catch
语句的主体可以直接访问到异常对象。 - 在常量表达式和常量初始化中,允许
return value optimization(RVO)
,禁止named return value optimization(NRVO)
。
struct A {
void *p;
constexpr A(): p(this) {}
};
constexpr A g()
{
A a;
return a;
}
constexpr A a; // a.p points to a
// constexpr A b = g(); //b.p将会空悬,因为指向了一个临时量
void h()
{
A c = g(); // c.p 可能指向c或者一个临时量
}
extern const A d;
constexpr A f()
{
A e;
if (&e == &d)
return A();
else
return e;
// 在常量环境下,强制执行NRVO会导致冲突
// 在非常量环境下,才会执行NRVO
}
// constexpr A d = f(); // d.p将会空悬
例子
#include <iostream>
#include <vector>
struct Noisy {
Noisy() { std::cout << "constructed at " << this << '\n'; }
Noisy(const Noisy&) { std::cout << "copy-constructed\n"; }
Noisy(Noisy&&) { std::cout << "move-constructed\n"; }
~Noisy() { std::cout << "destructed at " << this << '\n'; }
};
std::vector<Noisy> f() {
std::vector<Noisy> v = std::vector<Noisy>(3); //临时量初始化v发生拷贝省略
return v; // 执行NRVO优化
} // 如果不能执行NRVO优化,则调用移动构造函数
void g(std::vector<Noisy> arg) {
std::cout << "arg.size() = " << arg.size() << '\n';
}
int main() {
std::vector<Noisy> v = f(); // copy elision in initialization of v
// from the temporary returned by f() (until C++17)
// from the prvalue f() (since C++17)
g(f()); //函数g()参数初始化发生拷贝省略操作
}
输出
constructed at 0x2145c20
constructed at 0x2145c21
constructed at 0x2145c22
constructed at 0x2146c50
constructed at 0x2146c51
constructed at 0x2146c52
arg.size() = 3
destructed at 0x2146c50
destructed at 0x2146c51
destructed at 0x2146c52
destructed at 0x2145c20
destructed at 0x2145c21
destructed at 0x2145c22