# C++中的返回值优化 在这边文章里用到了以下编译器和操作系统,大家请自行安装 ``` $ uname -ar Linux debian 4.9.0-3-amd64 #1 SMP Debian 4.9.30-2+deb9u3 (2017-08-06) x86_64 GNU/Linux $ lsb_release -a No LSB modules are available. Distributor ID: Debian Description: Debian GNU/Linux 9.1 (stretch) Release: 9.1 Codename: stretch $ clang++ --version clang version 5.0.0 (tags/RELEASE_500/final) Target: x86_64-unknown-linux-gnu Thread model: posix $ g++ --version g++ (Debian 6.3.0-18) 6.3.0 20170516 Copyright (C) 2016 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. ``` 首先我们先来看一道题,下面的代码运行之后会输出什么结果? ```clike= #include <iostream> using namespace std; class C { public: C() { cout << "Constructor" << endl; } C(const C&) { cout << "Copy Constructor" << endl; } ~C() { cout << "Destructor" << endl; } }; C foo() { C c1; return c1; } int main() { foo(); return 0; } ``` ``` A. Constructor Copy Constructor Destructor Destructor B. Constructor Destructor ``` 我想大多数人会选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函数中加上一个无用的条件判断语句,试试编译器是否能继续应用返回值优化。 ```clike= #include <iostream> using namespace std; class C { public: C() { cout << "Constructor" << endl; } C(const C&) { cout << "Copy Constructor" << endl; } ~C() { cout << "Destructor" << endl; } }; C foo() { C c1, c2; if (true) { return c1; } else { return c2; } } int main() { foo(); return 0; } ``` 可以发现,在上面的代码中,如果编译器足够聪明,它应该能推断出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. 是否调用拷贝构造函数和析构函数的决定交给了编译器实现去判断,所以在实际工程中为了写出可移植的代码,就需要避免在构造函数和析构函数中加入有副作用的语句,并且应该尽量把复杂的逻辑剥离出来,放在类的其它成员函数中实现。