为什么复制构造函数不能传值
在剑指offer中看到一个例题,不懂为什么在复制构造函数中不能传值,书里说会导致不断递归导致栈溢出。以下是源代码和题目:
#include <iostream> using namespace std; class A{ private: int value; public: A(int n){value = n;} A(A other){value = other.value;} void Print() { cout << value << endl;} }; int main() { A a = 10; //constructor A b = a; //copy constructor b.Print(); return 0; }
选项为:
A.编译错误
B.编译成功,运行时程序崩溃
C.编译运行正常,输出10
答案: A、编译错误。复制构造函数A(Aother)传入的参数是A的一个实例。由于是传值参数,我们把形参复制到实参会调用复制构造函数。因此如果允许复制构造函数传值,就会在复制构造函数内调用复制构造函数,就会形成永无休止的递归调用从而导致栈溢出。
重点: C++的标准不允许复制构造函数传值参数,只能将构造函数修改为A(const A& other),也就是把传值参数改为常量引用。(注意:传指针也是不可以的,只能改为引用)。
下面用例子解释一下为什么会无限递归下去:
#include <iostream>
using namespace std;
class CTest{
private:
int n;
public:
CTest(int i):n(i){
cout << "Constructor with argument" << endl;
} //带参构造函数
CTest(const CTest &ctest){
n = ctest.n;
cout << "Copy constructor" << endl;
}
//赋值运算符重载
CTest& operator=(const CTest &t){
cout << "Assignment operator" << endl;
n = t.n;
return *this;
}
void myTestFunc(CTest t){
}
};
int main()
{
CTest a(2);
CTest b(3);
b = a; //这里是赋值运算符
CTest c = a;
c.myTestFunc(a);
return 0;
}
输出如下:
Constructor with argument
Constructor with argument
Assignment operator
Copy constructor
Copy constructor
分析一下代码执行过程:
- 首先
CTest a(2);
执行,会调用带参数的构造函数,于是输出的第一行Constructor with argument
是这里引起的; - 同理
CTest b(3);
也是一样; - 然后
b = a;
这句因为b和a已经实例化了,所以不用构造函数进行实例化,直接调用赋值运算符,输出第三行的Assignment operator
; - 接着
CTest c = a;
,由于c
没有实例化,因此需要调用构造函数来对c
进行实例化,参数为a
,符合复制构造函数,因此调用复制构造函数执行,输出第四行的Copy constructor
; - 最后到了
c.myTestFunc(a);
,这里的意思是c
调用自己的成员函数,传入参数为a
,由于a
是实参,成员函数是形参,因此需要复制a
的一个副本,可以看成CTest t = a
。这时候需要调用复制构造函数来对t进行实例化,因此输出第六行的Copy constructor
。
以上是复制构造函数的参数为引用时的情况,如果复制构造函数参数为传值的方式,我们可以复盘一下程序的执行:
-
同
1
; -
同
2
; -
同
3
; -
在
CTest c = a;
这里,需要调用复制构造函数来对c
进行实例化,也就是执行CTest(CTest t)
这个构造函数,要执行这个函数,我们传入的参数是a
,a
是实参,t
是形参,这样的话我们需要将a
复制一份给t
,也就是执行CTest t = a
,这个形式和一开始的CTest c = a
是一样的,于是就这样无限不循环下去了,最后导致栈溢出。正常的情况下,也即复制构造函数的参数为引用的情况下,
CTest c = a;
这条语句是调用复制构造函数,也就是CTest(const CTest &ctest)
,由于参数是引用,所以不用进行形参和实参之间的复制即可直接调用。
所以,拷贝构造函数的参数使用引用类型不是为了减少一次内存拷贝,而是避免拷贝构造函数无限制的递归下去。
总结一下哪几种情况会调用复制构造函数:
- 显示或隐式地用同类型的一个对象来初始化另一个对象,如上例中的
CTest c = a;
- 作为实参传递给一个函数。如上例中的
c.myTestFunc(a);
- 在函数体内返回一个对象时,也会调用返回值类型的复制构造函数
- 初始化序列容器的元素时。如
vector<string> svec(5);
string的缺省构造函数和拷贝构造函数都会被调用。–暂时不大懂 - 用列表的方式初始化数组元素时。
string a[] = {string("hello"), string("world")};
, 会调用string的拷贝构造函数。
参考:
- https://blog.csdn.net/xiaoquantouer/article/details/70145348