分享

拷贝构造函数的参数类型必须是引用(转)

 susongdada 2014-03-14

 在C++中, 构造函数,拷贝构造函数,析构函数和赋值函数(赋值运算符重载)是最基本不过的需要掌握的知识。 拷贝构造函数的参数为什么必须使用引用类型?

原因:
       如果拷贝构造函数中的参数不是一个引用,即形如CClass(const CClass c_class),那么就相当于采用了传值的方式(pass-by-value),而传值的方式会调用该类的拷贝构造函数,从而造成无穷递归地调用拷贝构造函数。因此拷贝构造函数的参数必须是一个引用。
       需要澄清的是,传指针其实也是传值,如果上面的拷贝构造函数写成CClass(const CClass* c_class),也是不行的。事实上,只有传引用不是传值外,其他所有的传递方式都是传值。

       先从一个小例子开始:(自己测试一下自己看看这个程序的输出是什么?)

  1. #include<iostream>  
  2. using namespace std;  
  3.   
  4. class CExample  
  5. {  
  6. private:  
  7.     int m_nTest;  
  8.   
  9. public:  
  10.     CExample(int x) : m_nTest(x)      //带参数构造函数  
  11.     {   
  12.         cout << "constructor with argument"<<endl;  
  13.     }  
  14.   
  15.     // 拷贝构造函数,参数中的const不是严格必须的,但引用符号是必须的  
  16.     CExample(const CExample & ex)     //拷贝构造函数  
  17.     {  
  18.         m_nTest = ex.m_nTest;  
  19.         cout << "copy constructor"<<endl;  
  20.     }  
  21.   
  22.     CExample& operator = (const CExample &ex)   //赋值函数(赋值运算符重载)  
  23.     {     
  24.         cout << "assignment operator"<<endl;  
  25.         m_nTest = ex.m_nTest;  
  26.         return *this;  
  27.     }  
  28.   
  29.     void myTestFunc(CExample ex)  
  30.     {  
  31.     }  
  32. };  
  33.   
  34. int main(void)  
  35. {  
  36.     CExample aaa(2);  
  37.     CExample bbb(3);  
  38.     bbb = aaa;  
  39.     CExample ccc = aaa;  
  40.     bbb.myTestFunc(aaa);  
  41.   
  42.     return 0;     
  43. }  
这个例子的输出结果是:

  1. constructor with argument        // CExample aaa(2);  
  2. constructor with argument        // CExample bbb(3);  
  3. assignment operator              // bbb = aaa;  
  4. copy constructor                 // CExample ccc = aaa;  
  5. copy constructor                 //  bbb.myTestFunc(aaa);  

第三个输出: assignment operator                // bbb = aaa;

第四个输出: copy constructor                      // CExample ccc = aaa;

这两个得放到一块说。 肯定会有人问为什么两个不一致。原因是, bbb对象已经实例化了,不需要构造,此时只是将aaa赋值给bbb,只会调用赋值函数! 但是ccc还没有实例化,因此调用的是拷贝构造函数,构造出ccc,而不是赋值函数!

第五个输出: copy constructor                      //  bbb.myTestFunc(aaa);

实际上是aaa作为参数传递给bbb.myTestFunc(CExample ex), 即CExample ex = aaa;和第四个一致的, 所以还是拷贝构造函数,而不是赋值函数。

通过这个例子, 我们来分析一下为什么拷贝构造函数的参数只能使用引用类型。

看第四个输出: copy constructor                      // CExample ccc = aaa;

构造ccc,实质上是ccc.CExample(aaa); 我们假如拷贝构造函数参数不是引用类型的话, 那么将使得 ccc.CExample(aaa)变成aaa传值给ccc.CExample(CExample ex),即CExample ex = aaa,因为 ex 没有被初始化, 所以 CExample ex = aaa 继续调用拷贝构造函数,接下来的是构造ex,也就是 ex.CExample(aaa),必然又会有aaa传给CExample(CExample ex), 即 CExample ex = aaa;那么又会触发拷贝构造函数,就这下永远的递归下去。

所以绕了那么大的弯子,就是想说明拷贝构造函数的参数使用引用类型不是为了减少一次内存拷贝, 而是避免拷贝构造函数无限制的递归下去。

还有一点需要说明,以引用返回函数值,定义函数时需要在函数名前加&,用引用返回一个函数值的最大好处是,在内存中不产生被返回值的副本。当函数的返回值是对象时,返回类型若不是引用的时候,只是一个简单的对象,此时需要调用拷贝构造函数,否则,如果是引用的话就不需要调用拷贝构造函数。

若去掉代码22行中的引用符号,第三个输出就会是: 

                           assignment operator                // bbb = aaa;

                           copy constructor                       // bbb = aaa;

引用作为返回值,必须遵守以下规则: 

(1)不能返回局部变量的引用。主要原因是局部变量会在函数返回后被销毁,因此被返回的引用就成为了"无所指"的引用,程序会进入未知状态。 

(2)不能返回函数内部new分配的内存的引用。虽然不存在局部变量的被动销毁问题,可对于这种情况(返回函数内部new分配内存的引用),又面临其它尴尬局面。例如,被函数返回的引用只是作为一 个临时变量出现,而没有被赋予一个实际的变量,那么这个引用所指向的空间(由new分配)就无法释放,造成memory leak。 

(3)可以返回类成员的引用,但最好是const。主要原因是当对象的属性是与某种业务规则(business rule)相关联的时候,其赋值常常与某些其它属性或者对象的状态有关,因此有必要将赋值操作封装在一个业务规则当中。如果其它对象可以获得该属性的非常 量引用(或指针),那么对该属性的单纯赋值就会破坏业务规则的完整性。

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多