拷贝构造函数
通常在如下情况下会使用拷贝构造函数:
-
使用另一个同类型的对象来初始化新创建的对象。
-
复制对象把它作为参数传递给函数。
-
复制对象,并从函数返回这个对象。
拷贝构造函数的常见结构:
classname (const classname &obj) { // 构造函数的主体 }
值传递方式
#include <iostream> using namespace std; class A { public: A() { cout << "construct" << endl; } A(const A& a) // 拷贝构造函数 { cout << "copy construct" << endl; } ~A() { cout << "destruct" << endl; } }; A fun() // 全局函数 { return A(); } int main() { A a = fun(); return 0; }
类作为特殊的数据类型也可以通过值传递的方式进行对象的传递和拷贝,上述对象的值传递过程需要进行如下两次对象拷贝:
-
fun()函数的局部对象【拷贝】到main()中的临时对象1,作为返回值;
-
临时对象1【拷贝】到对象a。
引用传递方式
通常类的数据量较大,采用传值的方式传递和返回对象会调用拷贝构造函数进行对象的拷贝操作,会带来大量内存空间的开辟和释放,导致性能的损耗、降低效率。因此通常采用引用或指针的方式进行对象的传递,而实际上针对对象的操作通常都使用引用来进行。
对象引用作为返回值的一般框架:
A& fun(A& a) // 全局函数 { return a; } int main() { A a1; A& a2 = fun(a1); return 0; }
这里的fun函数采用对象的引用作为形参,这么做的目的是确保在返回对象的引用时,返回的对象不是临时变量或局部变量,如果是返回的对象是临时变量或局部变量,一旦该函数运行结束,局部变量或临时变量很可能会被系统销毁,导致编译错误。即使系统没有报错,那也是因为g++编译时会进行返回值优化,此时执行的流程是:
-
fun()函数的局部变量在被销毁之前,系统调用【拷贝】构造函数创建临时变量1;
-
a2直接【引用】该临时变量1(避免了上述传递过程中的临时变量1到变量a的拷贝操作)。
右值引用
除了返回对象引用的方式之外,还可以通过右值引用的方式减少拷贝构造函数的使用次数。
A fun() // 全局函数 { return A(); } int main() { A&& a = fun(); // 常量左值引用 return 0; }
右值引用绑定了右值,让fun()返回的临时对象的生命周期延长了(与a一样长),其执行过程与引用传递大致相同,即只进行了由局部变量到临时变量1的拷贝过程,避免了上述值传递过程中的临时变量1到变量a的拷贝过程。
备注:
1、直接运行上述程序,可能有些流程打印不出来,原因:c++11 测试右值 临时对象的构造 编译器会自动优化导致有些流程未打印
需要添加-fno-elide-constructors标记
参考:
1、-fno-elide-constructors:https://blog.csdn.net/with_dream/article/details/85131151