C++复制构造函数分析

来源:互联网 发布:切换城市js代码 编辑:程序博客网 时间:2024/05/23 07:26

复制构造函数(copy constructor)是一种特殊的构造函数,具有单个形参,该形参(通常也是const)是该类引用。当定义一个新对象并用一个同类型的对象对它进行初始化时,将显式使用复制构造函数,当将该类对象传递给函数或函数返回该类型的对象时,将隐式的使用复制构造函数。如果没有类没有定义复制构造函数,由编译自动为类合成复制构造函数。

1.复制构造可以编译器隐式调用,具体可分为以下几种情况

(1)  根据另一个同类型的对象显示或隐式的初始化一个对象,通过一个同类对象初始化一个新对象是调用其复制构造函数完成的。

(2)  一个对象,将它作为实参传给一个函数,如果函数(包括成员函数)的参数为类类形的,那么调用过程中先通过复制构造函数构造临时副本,把参数传入函数的栈区,等函数调用返回时再调用类的析构函数,销毁临时副本对象。

(3)  从函数返回一个复制对象,  与实参传入函数相同。

(4)  初始化顺序容器的元素

复制构造函数可用于初始化顺序容器中的元素,

如vector<Classname>svec(10);

编译器使用Classname的默认构造函数创建一个临时对象,后再用复制构造函数来初始化svec元素,每元素重复这个过程。

(5)  根据元素初始化式列表初始化数组

定义类类形数组时,如果没有为类类形数组提供初始化元素,则由默认构造函数初始化一个临时对象后,再用复制构造函数初始化数组元素,如果指定了适当的类型元素,则通过复制构造函数初始化元素。


以下用一个简单的Book类来说明以上各种情况

[cpp] view plaincopyprint?在CODE上查看代码片派生到我的代码片
  1. class Book  
  2. {  
  3. public:  
  4.     Book(string name,int num):m_strName(name),m_nNum(num),  
  5.         m_ptPrice(new double(10.0))   
  6.     {  
  7.         cout<<m_strName<<"  Constructor"<<endl;  
  8.     }  
  9.     Book(string name = "default"):m_strName(name)  
  10.     {  
  11.         m_nNum = 100;  
  12.         m_ptPrice = new double(999);  
  13.         cout<<m_strName<<"  Constructor"<<endl;  
  14.     }  
  15.     Book(const Book &b)  
  16.     {  
  17.         m_nNum = b.m_nNum;  
  18.         m_strName = b.m_strName;  
  19.         m_ptPrice = new double(*b.m_ptPrice);  
  20.         cout<<m_strName<<"  copy constructor..."<<endl;  
  21.     }  
  22.     const Book operator=(const Book b)  
  23.     {  
  24.         m_nNum = b.m_nNum;  
  25.         m_strName = b.m_strName;  
  26.         m_ptPrice = new double(*b.m_ptPrice);  
  27.         cout<<m_strName<<" operator= call..."<<endl;  
  28.         return *this;  
  29.   
  30.     }  
  31.     void DisplayBookInf()  
  32.     {  
  33.         cout<<"BookName: "<<m_strName  
  34.             <<" Num: "<<m_nNum<<" Price: "<<*m_ptPrice<<endl;  
  35.     }  
  36.     ~Book()  
  37.     {  
  38.         //delete m_ptPrice;  
  39.         cout<<m_strName<<"  Dstructor"<<endl;  
  40.     }  
  41. public:  
  42.     string m_strName;  
  43.     int m_nNum;  
  44.     double *m_ptPrice;  
  45.       
  46. };  

下面是各种情况下实现
[cpp] view plaincopyprint?在CODE上查看代码片派生到我的代码片
  1. int _tmain(int argc, _TCHAR* argv[])  
  2. {  
  3. cout<<"语句:Book b1(\"c++ primer\",100);"<<endl;  
  4.     Book b1("c++ primer",100);//两个参数的构造函数调用  
  5.   
  6.     cout<<"语句:Book b2 = b1;"<<endl;  
  7.     Book b2 = b1;//复制构造函数调用  
  8.   
  9.     cout<<"语句: b3(b2);"<<endl;  
  10.     Book b3(b2);//复制构造函数调用  
  11.   
  12.     cout<<"语句:Book b4 = \"thinking in c++\";"<<endl;  
  13.     Book b4 = "thinking in c++";//一个参数构造函数调用  
  14.   
  15.     cout<<"语句:GetBook(b1)"<<endl;  
  16.     GetBook(b1);//复制构造函数与析构函数调用各调用两次  
  17.   
  18.     cout<<"TotalValue(b4) "<<endl;  
  19.     TotalValue(b4);//引用调用不会有构造函数与析构函数调用  
  20.   
  21.     cout<<"语句:vector<Book> vec(2)"<<endl;  
  22.     vector<Book> vec(2);//先调用默认构造函数,再调用复制构造函数,两次  
  23.   
  24.     cout<<"语句:BookArrary bArray[2] = {Book(\"thinking in jave\"),b4};"<<endl;  
  25.     BookArrary bArray[2] = {Book("thinking in jave"),b4};  
  26.   
  27.     cout<<"语句:b4 = b1;"<<endl;  
  28.     b4 = b1;//因为Book重载了"="所以这里调用的是operator =(const Book b)  
  29.   
  30.     cout<<"对象析构;"<<endl;  
  31.   
  32.     return 0;  
  33. }  

运行结果
[cpp] view plaincopyprint?在CODE上查看代码片派生到我的代码片
  1. 语句:Book b1("c++ primer",100);  
  2. c++ primer  Constructor  
  3. 语句:Book b2 = b1;  
  4. c++ primer  copy constructor...  
  5. 语句: b3(b2);  
  6. c++ primer  copy constructor...  
  7. 语句:Book b4 = "thinking in c++";  
  8. thinking in c++  Constructor  
  9. 语句:GetBook(b1)  
  10. c++ primer  copy constructor...  
  11. c++ primer  copy constructor...  
  12. c++ primer  Dstructor  
  13. c++ primer  Dstructor  
  14. TotalValue(b4)  
  15. 引用调用  
  16. 语句:vector<Book> vec(2)  
  17. default  Constructor  
  18. default  copy constructor...  
  19. default  Dstructor  
  20. default  Constructor  
  21. default  copy constructor...  
  22. default  Dstructor  
  23. 语句:BookArrary bArray[2] = {Book("thinking in jave"),b4};  
  24. thinking in jave  Constructor  
  25. thinking in jave  copy constructor...  
  26. thinking in jave  Dstructor  
  27. thinking in c++  copy constructor...  
  28. 语句:b4 = b1;  
  29. c++ primer  copy constructor...  
  30. c++ primer operator= call...  
  31. c++ primer  copy constructor...  
  32. c++ primer  Dstructor  
  33. c++ primer  Dstructor  
  34. 对象析构;  
  35. thinking in c++  Dstructor  
  36. thinking in jave  Dstructor  
  37. default  Dstructor  
  38. default  Dstructor  
  39. c++ primer  Dstructor  
  40. c++ primer  Dstructor  
  41. c++ primer  Dstructor  
  42. c++ primer  Dstructor  
  43. 请按任意键继续. . .  


2.类成员变量含有指针的复制

如果我们的类没有定义复制构造函数,则编译器自动合成一个复制构造函数。该复制构造函数在以上各种情况中被调用。默认的复制构造函数把一个类对象的成员变量逐个到另外一个类对象中。如果该对象有指针指向动态内存分配,则通过复制把指针变量的数值复制给新对象相应的指针变量,将会造成两个类对象同时指向同一个内存空间,如果其中有一个对象已经析构,则其指针变量指向的内存空间就会销毁,那么另外一个对象指针成员会指向已经销毁的空间,造成该对象的指针变量悬空,指向非法的空间。

如何解决上述问题呢,一般可以通过两种方法来实现,一是通过深度复制,即在类的复制构造函数中为类的指针变量动态分配内存空间,另一是通过定义智能指针类。

(1)第一种方法:深度复制

复制构造定义如下:

[cpp] view plaincopyprint?在CODE上查看代码片派生到我的代码片
  1. Book(const Book &b)  
  2. {  
  3.         m_nNum = b.m_nNum;  
  4.         m_strName = b.m_strName;  
  5.         m_ptPrice = new double(*b.m_ptPrice);//通过操作符new为指针分配内存空间  
  6.         //cout<<m_strName<<"  copy constructor..."<<endl;}  


在Book类中,为其定义了复制构造函数,并在该函数中为类的指针变量重新分配了内存空间,这样就不会将成将造成指针悬空。则通过Book b2(b1)将会造成下图的内存布局:


通过以上分析可知,通过深度复制可以解决指针悬空问题,但也有个缺点:如果动态分配的空间很大的话,通过复制构造定义多个对象则会造成很大空间浪费,因为它们动态内存空间里的数据都是一样的。所以下面介绍另外一种解决方法,智能指针。

(2)智能指针

定义智能指针有通用技术是采用使用计数

首先定义计数类:

[cpp] view plaincopyprint?在CODE上查看代码片派生到我的代码片
  1. class UseCount  
  2. {  
  3.    frend class ClassPT;  
  4.    int*m_ptValue;  
  5.    int m_nCount;  
  6.    UseCount(double *pVale):m_ptValue(pValue),m_nCount(1){}  
  7.    ~UseCount(){ delete m_ptValue;}  
  8. };  
[cpp] view plaincopyprint?在CODE上查看代码片派生到我的代码片
  1.   
[cpp] view plaincopyprint?在CODE上查看代码片派生到我的代码片
  1. ClassPT类作为UseCount,故ClassPT类可以访问UseCount计数类的所有成员变量。有了引用计数类之后 ,只需重写复制构造函数及”=”操作符即可。  
[cpp] view plaincopyprint?在CODE上查看代码片派生到我的代码片
  1.   
[cpp] view plaincopyprint?在CODE上查看代码片派生到我的代码片
  1. class ClassPT  
  2. {  
  3. public:  
  4.     ClassPT(int *pt, int val):m_ptr(new UseCount(p)),m_nValue(val){}  
  5.     ClassPT(const ClassPT &orig):m_ptr(orig.ptr),m_nValue(orig.m_nValue)  
  6.     {  
  7.     ++m_ptr. nCount;  
  8.     }  
  9.     ClassPT &operator=(const ClassPT &orig)  
  10.     {  
  11.          ++orig.m_ptr->m_nCount;  
  12.         if(--m_ptr->m_nCount == 0)  
  13.         {  
  14.             delete m_ptr;  
  15.         }  
  16.     }  
  17.     m_ptr = orig.m_ptr;  
  18.     m_nValue =orig.nValue;   
  19.     return *this;  
  20. }  
  21. private:  
  22.     UseCount m_ptr;  
  23.     int m_nValue;  
  24. };  


ClassPT对象构造时内存模型




ClassPT对象复制构造时内存模型


3.禁止复制
在很多情况下,复制函数的定义是必要的,然后在有些类需要完成禁止复制操作,如iostream类就不允许复制,但如果类不显示定义复制构造函数的话,编译器会为类合成一个一个默认复制构造函数,所以为的防止复制,应该把复制构造函数定义为私有的(不对其定义),声明不定义是合法的,使用任何未定义的成员都会导致链接失败。
0 0
原创粉丝点击