C++书写拷贝构造函数,重载赋值操作符和clone函数需要注意有关问题

C++书写拷贝构造函数,重载赋值操作符和clone函数需要注意问题

对于C++类:显示地写出拷贝构造函数,重载赋值操作符和析构函数是良好的习惯,但在写构造函数时需要注意一些容易出现的错误,如下面的代码:

 

#include <iostream>
using namespace std;
 
class M{
public:
    M(){}
    M(const M &m){
       cout<<"copy construtor"<<endl;
       operator =(m);
    }
    M operator =(const M &m){   //问题出在此处
       cout<<"operator ="<<endl;
       return *this;
    }
};
 
int main() {
    M m1;
    M m2;
    m2=m1;
    return 0;
}


在下面三种情况下会调用拷贝构造函数:

(1)用一个已经实例化了的该类对象,去实例化该类的另外一个对象;

(2)用该类的对象传值的方式作为一个函数的参数;

(3)一个函数返回值为该类的一个对象。

特别地,对于语句 M m;  M mm=m; 属于(1)情况,即语句M mm=m;调用的是拷贝构造函数,而不是构造函数。

 

但在重载=操作符时,返回值不是引用类型将导致程序运行出现严重问题。即如果出现上面会调用拷贝构造函数的三种情况之一,或者使用=操作符时,拷贝构造函数和operator =将循环递归调用,导致程序出现死循环。原因是拷贝构造函数和operator =之间不断地重复调用。

解决办法:将operator =的返回类型改为引用类型M&,此时调用operator =时不会去调用拷贝构造函数。

 

还有,若要写clone时,若通过下面的方式:

    M clone(){

       cout<<"clone"<<endl;

       return *this;

    }

前提是拷贝构造函数不能调用clone来完成拷贝,否则出现上面同样的问题,下面的代码就会出现这样的问题

    M(const M &m){

       cout<<"copy construtor"<<endl;

       clone();

    }

总之,在写这些函数时,要特别留意彼此的调用关系。

以下是我的惯用写法:

(A)对于拷贝构造函数和重载=操作符

    M(const M &m){

       cout<<"copy construtor"<<endl;

       operator =(m);

    }

    M& operator =(const M &m){  //问题出在此处

       cout<<"operator ="<<endl;

       /* 此处写上成员数据的拷贝 */

       return *this;

    }

这里写成了inline函数,只是方便说明问题,其实不必非要这么写,可以采取先声明,后定义的常规方法。

(B)对于clone函数

声明:  virtual M clone();   //考虑继承时的多态

定义:  M M::clone(){

       cout<<"clone"<<endl;

       //将在调用处直接调用构造函数,效率高,避免返回局部变量,更安全

       return M();

    }