为什么复制构造函数不能传值

为什么复制构造函数不能传值

在剑指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

分析一下代码执行过程:

  1. 首先CTest a(2);执行,会调用带参数的构造函数,于是输出的第一行Constructor with argument是这里引起的;
  2. 同理CTest b(3);也是一样;
  3. 然后b = a;这句因为b和a已经实例化了,所以不用构造函数进行实例化,直接调用赋值运算符,输出第三行的Assignment operator;
  4. 接着CTest c = a;,由于c没有实例化,因此需要调用构造函数来对c进行实例化,参数为a,符合复制构造函数,因此调用复制构造函数执行,输出第四行的Copy constructor;
  5. 最后到了c.myTestFunc(a);,这里的意思是c调用自己的成员函数,传入参数为a,由于a是实参,成员函数是形参,因此需要复制a的一个副本,可以看成CTest t = a。这时候需要调用复制构造函数来对t进行实例化,因此输出第六行的Copy constructor

以上是复制构造函数的参数为引用时的情况,如果复制构造函数参数为传值的方式,我们可以复盘一下程序的执行:

  1. 1

  2. 2

  3. 3

  4. CTest c = a;这里,需要调用复制构造函数来对c进行实例化,也就是执行CTest(CTest t)这个构造函数,要执行这个函数,我们传入的参数是aa是实参,t是形参,这样的话我们需要将a复制一份给t,也就是执行CTest t = a,这个形式和一开始的CTest c = a是一样的,于是就这样无限不循环下去了,最后导致栈溢出。

    正常的情况下,也即复制构造函数的参数为引用的情况下,CTest c = a;这条语句是调用复制构造函数,也就是CTest(const CTest &ctest),由于参数是引用,所以不用进行形参和实参之间的复制即可直接调用。

所以,拷贝构造函数的参数使用引用类型不是为了减少一次内存拷贝,而是避免拷贝构造函数无限制的递归下去。

总结一下哪几种情况会调用复制构造函数:

  1. 显示或隐式地用同类型的一个对象来初始化另一个对象,如上例中的CTest c = a;
  2. 作为实参传递给一个函数。如上例中的 c.myTestFunc(a);
  3. 在函数体内返回一个对象时,也会调用返回值类型的复制构造函数
  4. 初始化序列容器的元素时。如vector<string> svec(5); string的缺省构造函数和拷贝构造函数都会被调用。–暂时不大懂
  5. 用列表的方式初始化数组元素时。string a[] = {string("hello"), string("world")};, 会调用string的拷贝构造函数。

参考:

  1. https://blog.csdn.net/xiaoquantouer/article/details/70145348
  • 8
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

zedjay_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值