C++11以后,g++ 编译器默认开启复制省略(copy elision)选项,可以在以值语义传递对象时避免触发复制、移动构造函数。copy elision 主要发生在两个场景:
- 函数返回的是值语义时
- 函数参数是值语义时
返回值优化
返回值优化RVO(Return Value Optimization,RVO),即避免返回过程触发复制 / 移动构造函数。根据返回的值是否是匿名对象,可以分为两类:
- 具名返回值优化
NRVO
(Named Return Value Optimization,NRVO) - 匿名返回值优化
URVO
(Unknown Return Value Optimization,URVO )
二者的区别在于返回值是具名的局部变量(NRVO)还是无名的临时对象(URVO)。
假定现在有类Foo
,实现了复制构造函数(ctor
)、 移动构造函数(mtor
)。
class Foo {
public:
Foo() {
std::cout<<"default"<<std::endl; }
Foo(const Foo& rhs) {
std::cout<<"ctor"<<std::endl; }
Foo(Foo&& rhs) {
std::cout<<"mtor"<<std::endl; }
};
现在,有返回类型是Foo
的 两个函数:return_urvo_value
和 return_nrvo_value
,实现如下:
Foo return_urvo_value() {
return Foo{
};
}
Foo return_nrvo_value() {
Foo local_obj;
return local_obj;
}
按照常规,return_urvo_value
函数返回Foo{}
应该触发mtor
, return_nrvo_value
函数返回local_obj
应该触发ctor
。真的如此吗?
int main(int argc, char const *argv[]) {
auto x = return_urvo_value();
auto y = return_nrvo_value();
return 0;
}
输出如下:
g++ rvo.cc -o rvo && ./rvo
default
default
输出结果,令人惊讶!竟然都只调用了一次默认构造函数。这是因为编译器默认开启了RVO,为了禁止这个优化策略,需要为编译加上 -fno-elide-constructors
选项,此时输出如下:
$ g++ -fno-elide-constructors rvo.cc -o rvo && ./rvo
default
mtor
mtor
default
mtor
mtor
下面对输出结果,逐个分析。
URVO
首先,return_urvo_value
函数,触发两次移动构造函数,这很好理解:
- 基于return的
Foo{}
构造return_urvo_value
函数的返回值,触发一次; - 基于
return_urvo_value
函数返回的右值构造x
,触发一次。
return_urvo_value
函数return的Foo{}
,中间经过两次mtor
,才将Foo{}
的内部数据转移到了x
。但是,这中间的两次mtor
是可以避免的:由于return之后Foo{}
就结束生命周期,那为什么不直接将Foo{}
用于x
呢?
因此,编译器默认开启RVO&