C++拷贝构造、移动构造与返回值优化
拷贝构造函数
拷贝构造函数(又称复制构造函数),用来创建已存在对象的副本。用已有对象去初始化另一个对象。
还有拷贝赋值运算符,当需要显示地声明拷贝构造函数时,一般建议同时声明拷贝赋值运算符。
如果不声明拷贝构造函数(或拷贝赋值运算符),编译器将会生一个默认的拷贝构造函数(或拷贝赋值运算符)(当被调用时生成)。
注意:区分赋值和拷贝构造,也就是区分赋值与初始化。
class A {
int n;
public:
A(int n) :n{ n } { cout << "ctor" << endl; }
A(A& b) {
n = b.n;
cout << "Copy ctor" << endl;
}
A& operator = (const A& b) {
n = b.n;
cout << "=" << endl;
return *this;
}
};
int main() {
A a = 1;//调用的是ctor,而不是拷贝构造
A c{ 1 };//普通构造函数,输出ctor
A b{ a };//拷贝构造,输出Copy ctor
b = a; //赋值,输出=
return 0;
}
移动语义(移动构造、移动赋值)
C++11引用了移动语义,可以减少深拷贝的次数,提升效率。移动语义是与右值引用息息相关的。
右值引用
简单来说,可以取地址的是左值,不能取地址的是右值。
左值引用 &, 右值引用 &&。
class A {
public:
A() {};
// 移动构造函数
A(A && a) {
cout << "move" << endl;
};
};
int main() {
vector<A> v;
v.push_back(A());//A()是个临时对象,右值,所以调用move移动构造函数
补充: move()函数
move()可将左值变成右值
编译器优化
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