C++复制省略
2021-05-17
4 min read
C++ 复制省略
代码示例
在C++中,若要在函数中传递一个占用较多空间的对象到函数体外,一般做法是在函数体外定义变量,并传递指针或引用到函数中,示例代码如下:
// demo.h
class Demo {
public:
Demo() {std::cout << "Demo()" << std::endl;}
Demo(const Demo& rhs) {std::cout << "Demo(const Demo& rhs)" << std::endl;}
Demo& operator=(const Demo& rhs) {std::cout << "Demo& operator=(const Demo& rhs)" << std::endl;}
Demo(Demo&& rhs) {std::cout << "Demo(Demo&& rhs)" << std::endl;}
Demo& operator=(Demo&& rhs) {std::cout << "Demo& operator=(Demo&& rhs)" << std::endl;}
~Demo() {std::cout << "~Demo()" << std::endl;}
void update();
private:
int m_a = 10;
std::vector<double> m_x = {1.2, 2.4};
};
#include "demo.h"
void func(Demo& demo)
{
demo.update();
// do other things to demo.
}
int main()
{
Demo demo;
func(demo);
// ...
return 0;
}
上述方法无疑是正确的,且能保证性能,但是较为繁琐,而且存在一些潜在隐患;其实,在函数中直接返回一个对象更能反映上述代码的语义,同时更简洁优雅,示例代码如下:
#include "demo.h"
Demo func()
{
Demo demo
demo.update(a);
// do other things to demo.
return demo;
}
int main()
{
Demo demo = func();
// ...
return 0;
}
然而由于C++的值语义原因,在第二种方法中,当在函数中返回一个对象时,产生了多余的复制构造与析构操作,代价比较昂贵,所以要想性能优先,那么使用第一种方法更好;
但是,当我们将第二种方法的代码进行编译时,却意外地发现程序只进行了一次构造与析构,和第一种方法性能是一致的,终端输出如下:
// console :
Demo()
~Demo()
上述令我们感到惊讶的现象背后就是:编译器的复制省略(copy-elison)/返回值优化(rvo/nrvo)技术,此技术已经是C++标准(C++17)的一部分,所以此代码的性能在任何主流编译器的默认选项中都能得到保证;
若想要关闭此功能,以观察编译器原始的表现,可在CMakeLists.txt中进行如下设置:
set(CMAKE_C_FLAGS "-O0 -Wall -fno-elide-constructors")
set(CMAKE_CXX_FLAGS "-O0 -Wall -fno-elide-constructors")
重新编译后,终端输出如下:
// console :
Demo()
Demo(Demo&& rhs)
~Demo()
Demo(Demo&& rhs)
~Demo()
~Demo()
上述结果符合我们预期,产生了两次额外移动构造与析构操作,第一次是函数返回一个对象到临时空间中,另一次是临时对象赋值给目标变量;
最佳实践
现代C++推荐我们在函数中直接返回一个对象,这可让代码更为简洁,而且其性能由C++标准保证,所以在编写新代码时,尽量使用现代C++推荐的方式;
版权声明:
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明出处!