C++拷贝构造、移动构造与返回值优化
2017-07-03 21:46
246 查看
转自:http://findingsea.github.io/2017/06/21/C-copy-constructor-move-constructor-and-return-value-optimization/?utm_source=tuicool&utm_medium=referral
拷贝构造函数(又称复制构造函数),是用来创建已存在对象的副本。对应的还有一个概念是拷贝赋值运算符,当需要显示地声明拷贝构造函数时,一般建议同时声明拷贝赋值运算符,以使得代码的含义明确。
如果不声明拷贝构造函数(或拷贝赋值运算符),编译器将会生一个默认的拷贝构造函数(或拷贝赋值运算符)(当被调用时生成)。参阅 声明构造函数的规则 。
看一段典型的代码:
输出如下:
需要注意的是,如果写成
上文的程序中,
因此,C++11中引入了
输出:
可以看到,当再需要
这一点是我在编写样例代码时遇到的问题,讲到拷贝构造函数时,常举的一个例子如下:
这是很经典的例子,很多文章都会讲
然而,如果你现在写了这么一个程序,真的跑一遍,它什么都不会输出。
不要怀疑自己,代码没错,教程也没错,这是因为GCC的 返回值优化 。
当一个函数返回一个对象实例,一个临时对象将被创建并通过复制构造函数把目标对象复制给这个临时对象。C++标准允许省略这些复制构造函数,即使这导致程序的不同行为,即使编译器把两个对象视作同一个具有副作用。
这是C++标准允许编译器独立实现的优化。被称为返回值优化(RVO/NRVO)。
关于RVO/NRVO历史可以参见维基百科 Return
value optimization 词条
G++编译时,会默认对代码进行返回值优化,优化后的等价代码如下:
因此就不需要再调用
要关闭这种优化,只要在编译时加上
输出:
拷贝构造函数
拷贝构造函数(又称复制构造函数),是用来创建已存在对象的副本。对应的还有一个概念是拷贝赋值运算符,当需要显示地声明拷贝构造函数时,一般建议同时声明拷贝赋值运算符,以使得代码的含义明确。如果不声明拷贝构造函数(或拷贝赋值运算符),编译器将会生一个默认的拷贝构造函数(或拷贝赋值运算符)(当被调用时生成)。参阅 声明构造函数的规则 。
看一段典型的代码:
class TextFile { public: TextFile() {}; TextFile(const TextFile& tf) { cout << "copy" << endl; }; TextFile& operator=(TextFile& tf) { cout << "operator=" << endl; return *this; }; }; int main() { vector<TextFile> text_files; text_files.push_back(TextFile()); TextFile text_file_a; TextFile text_file_b; text_file_b = text_file_a; }
输出如下:
copy operator=
TextFile类显示地定义了拷贝构造函数与拷贝赋值运算符,分别在
text_files.push_back(TextFile());和
text_file_b = text_file_a;两处调用。
需要注意的是,如果写成
TextFile text_file_b = text_file_a;,那么被调用的将会是拷贝构造函数(也就是输出
copy)。
移动构造函数
上文的程序中, text_files.push_back(TextFile());该行构造了两个一模一样的
TextFile对象,一次是在
TextFile临时对象构造时,一次是向量在
push_back时将临时对象又拷贝一份,而第二次构造明显是可以避免的。
因此,C++11中引入了
移动构造的概念,与临时对象发生拷贝时不需要重新分配内存而是使用被拷贝对象的资源,即临时对象的资源。移动构造函数配合右值引用完成从临时对象数据转移到新的对象中,避免了数据拷贝。
class TextFile { public: TextFile() {}; // 移动构造函数 TextFile(TextFile && tf) { cout << "move" << endl; }; }; int main() { vector<TextFile> text_files; text_files.push_back(TextFile()); }
输出:
move
可以看到,当再需要
push_back时,调用的就是移动构造而不是拷贝构造函数了,其他情况同理,以此可以节省内存分配上的性能开支。
返回值优化
这一点是我在编写样例代码时遇到的问题,讲到拷贝构造函数时,常举的一个例子如下:class TextFile { public: TextFile() {}; TextFile(const TextFile& tf) { cout << "copy" << endl; }; TextFile& operator=(TextFile& tf) { cout << "operator=" << endl; return *this; }; }; TextFile get_tmp_text_file() { TextFile tf; return tf; } int main() { TextFile tf = get_tmp_text_file(); }
这是很经典的例子,很多文章都会讲
TextFile tf = get_tmp_text_file();会调用两次拷贝构造函数,一次是
get_tmp_text_file函数返回时,将函数返回拷贝到临时变量里,第二次是在
main函数内构造
TextFile对象时,将临时变量拷贝到
tf对象上。所以,程序会输出两遍
copy。
然而,如果你现在写了这么一个程序,真的跑一遍,它什么都不会输出。
不要怀疑自己,代码没错,教程也没错,这是因为GCC的 返回值优化 。
当一个函数返回一个对象实例,一个临时对象将被创建并通过复制构造函数把目标对象复制给这个临时对象。C++标准允许省略这些复制构造函数,即使这导致程序的不同行为,即使编译器把两个对象视作同一个具有副作用。
这是C++标准允许编译器独立实现的优化。被称为返回值优化(RVO/NRVO)。
关于RVO/NRVO历史可以参见维基百科 Return
value optimization 词条
G++编译时,会默认对代码进行返回值优化,优化后的等价代码如下:
TextFile get_tmp_text_file(TextFile * tf) { // 直接在tf上构造 } int main() { TextFile tf; get_tmp_text_file(&tf); }
因此就不需要再调用
TextFile对象的拷贝构造函数。
要关闭这种优化,只要在编译时加上
-fno-elide-constructors强制G++总是使用拷贝构造函数。
g++ 2DimensionArray.cpp -o run -fno-elide-constructors && ./run
输出:
copy copy
相关文章推荐
- c++构造,拷贝构造,拷贝赋值,移动构造,移动赋值
- c++的拷贝构造、拷贝赋值、移动构造、移动赋值
- C++中的拷贝构造,赋值和移动构造
- 现代c++之移动构造, 移动赋值, 拷贝构造, 拷贝赋值
- C++模板拷贝构造
- C++拷贝构造和赋值构造
- 《inside the c++ object model》读书笔记 之五 构造,解构,拷贝语意学
- C++ 对象构造, 拷贝, 赋值和隐式类型转换总结
- C++ 拷贝构造和赋值运算符重载
- C++拾遗--构造函数(二)拷贝构造
- 深度探索C++对象模型:5.构造、析构、拷贝语意学
- C++拷贝构造、临时对象、虚拟继承、重载、覆盖、隐藏
- c++拷贝构造
- 深入C++的拷贝构造和赋值函数 (深拷贝,浅拷贝)
- C++对象模型 第五章 构造、析构、拷贝语意学
- C++拷贝构造和赋值重载
- 用汇编的眼光看C++(之class构造、析构) ,(之拷贝、赋值函数)
- C++对象模型 ch5 构造 析构 拷贝语义学
- 深入探索C++对象模型--详解深拷贝与浅拷贝以及拷贝构造的建构
- 深度探索C++对象模型 第五章 构造、析构、拷贝语意学