浅谈c/c++中类的拷贝构造函数和重载赋值函数

拷贝构造函数
  拷贝构造函数,顾名思义,在拷贝的过程中进行构造类对象。首先看一个例子进行理解。
一个例子

  8 #include
 10 using namespace std;
 11 
 12 class copyconstructor
 13 {
 14     static int objectCount;
 15     public:
 16         copyconstructor():bufsize(0){objectCount++;}
 41         static void printOjectNum(const string& msg = "")
 42         {
 43             if(msg.size()!=0)
 44             {
 45                 cout<< msg<< ":";
 46                 cout<< "objectCount = "<< objectCount<< endl;
 47             }
 48         }
 49         ~copyconstructor()
 50         {
 52             objectCount--;
 53             cout<< "~copyconstructor():"<< objectCount<< endl;
 54         }
 55     private:
 57         int bufsize;
 58 };
 59 int copyconstructor::objectCount = 0;

main.cpp:

 8 #include
 9 #include"copyconstructor.h"
 11 using namespace std;
 12 
 13 copyconstructor f(copyconstructor c)
 14 {
 15     c.printOjectNum("after f()");
 16     return c;
 17 }
 18 
 19 int main()
 20 {
 21     copyconstructor c1;
 23     copyconstructor::printOjectNum("the objects num");
 25     copyconstructor c2 = f(c1);
 43     //c2 = c1;
 44     copyconstructor::printOjectNum("after call f(), the objects num");
 45     return 0;
 46 }

  在copyconstructor.h文件中定义了静态成员变量来记录外部申请对象实例的个数,执行main函数得到以下结果:
浅谈c/c++中类的拷贝构造函数和重载赋值函数_第1张图片
  可以发现在退出程序的时候,对象实例并未变为,而是-2。这是由于程序在调用f(c1)的时候调用了默认拷贝构造函数,进行了浅拷贝操作(c中的位操作),并未调用构造函数,当f函数返回值拷贝到c2后,f函数参数c1的拷贝会调用析构函数结束自己的生命周期。此时对象变为0,这个时候main函数内还存在c1和c2没有释放,当main函数结束时,自动调用析构,objectCount的值自然变成了-2。
自定义拷贝构造函数
  在上面提到了默认拷贝构造函数,在我们定义一个类的时候,如果没有定义默认拷贝构造函数,c++会有一个默认拷贝构造函数,在函数参数为值传递或者对象初始化使用另一个对象赋值的时候会调用拷贝构造函数拷贝一份对象的实例。如果我们的类很简单,内部没有指针成员变量和其他类对象等,使用默认拷贝构造函数没有问题。但是当一个类很复杂的时候我们必须定义自己的拷贝构造函数或者禁用拷贝构造函数。因为默认拷贝构造函数使用的是c的浅拷贝方式对对象实例进行拷贝的,也就是有指针成员变量的时候,如果调用了默认拷贝构造函数,就会出现指针共享的情况。这样很容易出现使用野指针或者多次释放野指针的情况。所以我们最好定义一个类的时候总是定义它的拷贝构造函数或者禁用。
   为上面的类添加一个指针成员变量char* buffer和自定义拷贝构造函数:

 18         copyconstructor(const copyconstructor& cop)
 19         {
 20             objectCount++;
 21             bufsize = cop.bufsize;
 22             buffer = new char[bufsize];
 23             memcpy(buffer, cop.buffer, bufsize*sizeof(char));
 24         }

 36         void setBuf(const int size)
 37         {
 38             bufsize = size;
 39             buffer = new char[bufsize];
 40             memset(buffer, 0, bufsize*sizeof(char));
 41         }

main.cpp

 19 int main()
 20 {
 21     copyconstructor c1;
 23     copyconstructor::printOjectNum("the objects num");
 25     copyconstructor c2 = f(c1);     //调用了拷贝构造函数
 43     copyconstructor c3 = c2;    //调用了拷贝构造函数
 44     copyconstructor::printOjectNum("after call f(), the objects num");
 45     return 0;
 46 }

输出结果:
浅谈c/c++中类的拷贝构造函数和重载赋值函数_第2张图片
这个时候类实例的个数是正确的。
当一个类比较复杂的时候(类成员含有其他类的对象,其他类又包含指针等类对象),这时为了深拷贝定义拷贝构造函数就很麻烦了,此时我们可以将拷贝构造函数声明为私有的,并且不去定义它。或者在声明的拷贝构造函数后添加=delete关键字即可。
重载赋值函数
对上面的c3做如下的改动:

copyconstructor c3 = f(c1);
c3 = c2;          //此时调用了赋值函数

  如果我们没有在类中重载赋值函数,会出现什么结果呢。程序同样会做浅拷贝操作,即拷贝buffer的时候会出现:拷贝buffer指针而非指针所指向的内容。此时会出现一个非常严重的问题。c3原指针成员变量所指向的内存没有释放!c3.buffer直接指向了c2.buffer所指向的内存。这里不但出现了共享指针的危险,更要命的是出现了内存泄露。如果这段代码在一个死循环内,且该对象占用内存很大的话,后果显而易见,宕机宕机!
  所以定义一个类的时候,对它的重载赋值函数定义或者禁用同样是很有必要的。
  下面是对该类的重载赋值函数的定义:

 26         copyconstructor& operator = (const copyconstructor& rightObj)
 27         {
 28             bufsize = rightObj.bufsize;
 29             if(NULL != buffer) delete []buffer;
 30             buffer = new char[bufsize];
 31             memcpy(buffer, rightObj.buffer, bufsize*sizeof(char));
 32             return *this;
 33 
 34         }

同样我们可以将其声明为一个私有成员函数或者添加delete关键字。

至于拷贝构造函数和赋值函数为什么要使用对象的引用呢,这是因为如果这个类是一个派生类的话,为了在构造派生类的时候可以将该派生类中的基类部分也可以完整的拷贝过来。即为了保证一个完整的对象被拷贝。

你可能感兴趣的:(C/C++)