关于c++中不允许复制构造函数传值参数的理解

来源:互联网 发布:查看网络延时命令 编辑:程序博客网 时间:2024/06/05 08:02

问题源于看剑指offer上的一道面试题,题目如下:

class A{private:    int value;public:    A(int n){ value = n; }    A(A other){ value = other.value; }    void Print() {cout<<value<<endl; }};int main(void){    A a = 10;    A b = a;    b.Print();    return 0;}

对上面这段代码进行分析编译运行的结果是:

A、编译错误   B、编译成功,运行时程序崩溃     C、编译运行正常,输出10

 

答案:A、编译错误。复制构造函数A(Aother)传入的参数是A的一个实例。由于是传值参数,我们把形参复制到实参会调用复制构造函数。因此如果允许复制构造函数传值,就会在复制构造函数内调用复制构造函数,就会形成永无休止的递归调用从而导致栈溢出。

 

敲黑板划重点:C++的标准不允许复制构造函数传值参数,只能将构造函数修改为A(const A& other),也就是把传值参数改为常量引用。(注意:传指针也是不可以的,只能改为引用)。

 

一开始不理解为什么会无限循环递归下去这个过程。于是在网上查了一下。用下面这个例子来深入理解一下这个过程。

 

#include<iostream>usingnamespace std;class CExample{private:    int m_nTest;public:    CExample(int x) : m_nTest(x)      //带参数构造函数    {        cout << "constructor withargument"<<endl;    }    // 拷贝构造函数,参数中的const不是严格必须的,但引用符号是必须的    CExample(const CExample & ex)     //拷贝构造函数    {        m_nTest = ex.m_nTest;        cout << "copyconstructor"<<endl;    }    CExample& operator = (const CExample&ex)   //赋值函数(赋值运算符重载)    {        cout << "assignmentoperator"<<endl;        m_nTest = ex.m_nTest;        return *this;    }    void myTestFunc(CExample ex)    {    }};int main(void){    CExample aaa(2);    CExample bbb(3);    bbb = aaa;    CExample ccc = aaa;    bbb.myTestFunc(aaa);    return 0;}

 

这段代码运行后输出如下:

constructorwith argument

constructorwith argument

assignmentoperator

copyconstructor

copyconstructor

 

程序分解分析:

第一个constructorwith argument,来源于CExample aaa(2);因为要对aaa实例化并初始化,所以要调用带参数的构造函数

第二个constructorwith argument,来源于CExample bbb(3);同理。

assignmentoperator,来源于bbb =aaa;

第一个copyconstructor,来源于CExample ccc = aaa;这个和上面为什么有区别呢?原因是因为bbb对象已经实例化了,不需要构造,所以将aaa赋值给bbb,只会调用赋值函数。但是ccc还没有实例化,因此调用的是拷贝构造函数,构造出ccc,而不是赋值函数。

第二个copyconstructor,来源于bbb.myTestFunc(aaa);。过程是这样:首先将aaa作为参数传递给bbb.myTestFunc(CExample ex),即CExample ex = aaa;和上面一样,所以还是拷贝赋值函数。

 

如果拷贝构造函数是传值而不是传引用,当调用ccc.CExample(aaa)时,aaa作为参数传值给ccc.CExample(CExample ex),即CExample ex = aaa,因为ex没有被初始化,所以会继续调用拷贝构造函数,接下来是构造ex,也就是ex.CExample(aaa),必然又会有aaa传给CExample(CExample ex),即CExample ex = aaa;那么又会触发拷贝构造函数,这样就无限递归下去了。

 

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

下面这几种情况下会调用拷贝构造函数

(1)显式或隐式地用同类型的一个对象来初始化另外一个对象。如上例中的CExample ccc = aaa;

(2)作为实参传递给一个函数。如上例中的bbb.myTestFunc(aaa);

(3)在函数体内返回一个对象时,也会调用返回值类型的拷贝构造函数

(4)初始化序列容器中的元素时。比如vector<string> svec(5),string的缺省构造函数和拷贝构造函数都会被调用。

(5)用列表的方式初始化数组元素时。string a[] = {string(“hello”),string(“world”)};会调用string的拷贝构造函数。

2 0
原创粉丝点击