C++返回值优化

首先我们先来看一道题,下面的代码运行之后会输出什么结果?
在这里插入图片描述
在这里插入图片描述
我想大多数人会选A,对吗?因为foo函数在返回C类的对象时会调用拷贝构造函数来创建一个临时对象。

现在让我们编译并运行这个程序,看看输出结果是否如我们所料

$ clang++ -std=c++11 foo.cpp

$ ./a.out

Constructor

Destructor

然而,遗憾的是,事实与课本里说的并不一样,那么,为什么会这样呢?从实际编译运行的输出来看,C类的拷贝构造函数并没有被调用。

这是因为在实际工程中大多数时候C++构造对象的开销巨大,编译器为了生成高效的代码,在foo函数返回时并没有调用拷贝构造函数去生成一个临时对象,而是直接使用在foo函数内初始化的对象c1作为返回值传出去。这就是C++中的返回值优化(Return Value Optimization, RVO)。

由于较新版本的gcc/clang均已默认开启了返回值优化(即使没有加上优化选项),我们想看到书上所说的现象只能在编译的时候加上特殊选项,让编译器不要做返回值优化,操作步骤如下:

$ clang++ -std=c++11 -fno-elide-constructors foo.cpp

$ ./a.out

Constructor

Copy Constructor

Destructor

Destructor

返回值优化能在一定程度上提高程序的运行效率,但是我们在实际工程中最好不要依赖这个编译器优化特性,因为它要求的条件非常苛刻(或者说,编译器还不够聪明:P)。让我们在foo函数中加上一个无用的条件判断语句,试试编译器是否能继续应用返回值优化。
在这里插入图片描述
可以发现,在上面的代码中,如果编译器足够聪明,它应该能推断出foo函数只会返回c1,所以可以对foo函数应用返回值优化,那么让我们来实际编译运行一下,看看执行情况。

$ clang++ -std=c++11 -O3 foo.cpp

$ ./a.out

Constructor

Constructor

Copy Constructor

Destructor

Destructor

Destructor

$ g++ -std=c++11 -O3 foo.cpp

$ ./a.out

Constructor

Constructor

Copy Constructor

Destructor

Destructor

Destructor

可以看到,clang 5.0.0和gcc 6.3.0即使把优化选项开到最大也不能对foo函数进行返回值优化。

返回值优化还带来了一个问题,那就是拷贝构造函数和析构函数的调用变得不可预测。如果在拷贝构造函数和析构函数中存在有副作用(side-effect)的语句,就会造成不同的编译器配置编译出来的程序运行结果不一致。事实上,这是被C++11标准所允许的,下面这段话引用自C++11标准Sec 12.8 Copying and moving class objects [class.copy]

When certain criteria are met, an implementation is allowed to omit the copy/move construction of a class object, even if the copy/move constructor and/or destructor for the object have side effects.

是否调用拷贝构造函数和析构函数的决定交给了编译器实现去判断,所以在实际工程中为了写出可移植的代码,就需要避免在构造函数和析构函数中加入有副作用的语句,并且应该尽量把复杂的逻辑剥离出来,放在类的其它成员函数中实现。

发布了282 篇原创文章 · 获赞 4 · 访问量 5541

猜你喜欢

转载自blog.csdn.net/it_xiangqiang/article/details/105233320