欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

《Effective C++》读书笔记 被你忽略的关于构造析构赋值

程序员文章站 2023-03-26 13:35:21
如果程序员没有定义,那么编译器会默认隐式为你创建一个copy构造函数,一个copy赋值操作符,一个析构函数。另外如果你没有声明任何构造函数,编译器会为你声明一个default构造函数。 但是只有当这些函数被用到时,他们才会被创建。例如Empty a(b),会创建copy构造函数。 [cpp] vie ......

如果程序员没有定义,那么编译器会默认隐式为你创建一个copy构造函数,一个copy赋值操作符,一个析构函数。另外如果你没有声明任何构造函数,编译器会为你声明一个default构造函数。

但是只有当这些函数被用到时,他们才会被创建。例如Empty a(b),会创建copy构造函数。

[cpp]  
  1. class Empty  
  2. {  
  3.     Empty(){...}//default构造函数,如果没有声明任何构造函数,编译器会自动声明  
  4.     Empty(const Empty& rhs){...}//copy构造函数,如果没有声明,编译器自动声明  
  5.     ~Empty(){}//析构函数,同copy构造函数  
  6.     Empty& operator=(const Empty& rhs){...}//copy复制操作符,同上  
  7. };  

对于copy赋值操作符,有一个需要注意的地方。考虑这样一个类NamedObject,包含一个string& nameValue和一个const int objectValue 。假设你使用编译器为你创建的copy赋值操作符,看看会发生什么:

[cpp]  
  1. string newDog("Persephone");  
  2. string oldDog("Satch");  
  3. NamedObject p(newDog,2);  
  4. NamedObject s(oldDog,36);  
  5. p=s;//将s拷贝赋值到p  

p的reference对象nameValue被重新赋值了!我们知道reference对象是不能变动的,因此这总是错误的。同样p的const int objectValue也被改变。

因此如果想在一个内含reference或const成员的类内支持赋值操作,你必须定义自己的copy赋值操作符

如果你不使用copy构造函数或者copy赋值操作符,你应该将他们声明为private并且不定义它。这样可以防止友元函数或成员函数或派生类使用它。

为多态基类声明virtual析构函数

为防止派生类在销毁时发生错误,即在delete派生类时只有基类部分被销毁,属于派生类的地方却还在,你应该将基类的析构函数声明为virtual。

同时,无端将所有类的析构函数声明为virtual是个错误的习惯,因为virtual会增加对象体积,造成不必要的资源浪费。一个心得是,在类内至少包含一个virtual函数时才为他声明virtual析构函数。

有时候你希望拥有一个抽象类,但是却没有纯虚函数,你可以为它声明一个纯虚析构函数,这样那个类就成为了抽象类。

绝不在构造和析构过程中调用virtual函数

当一个派生类调用构造函数时,若其调用了virtual函数,virtual函数调用的版本为其基类的版本,这不是我们希望看到的。这是因为在派生类对象的基类构造期间,对象的类型是基类而不是派生类。不只virtual函数会被编译器解析至基类,若使用运行期类型信息(dynamic_cast和typeid),也会把对象视为基类。

但是如果你想确保每次一个(继承体系上的)类的对象被创建,就会有适当版本的对象函数(多态函数)被调用,也就是在初始化的过程中调用。你可以将该函数声明为非virtual的,然后每次给他传递必要信息。即如下形式:

[cpp]  
  1. class Transaction  
  2. {  
  3. public:  
  4.     explicit Transaction(constant string& logInfo)  
  5.     {  
  6.         logTransaction(logInfo);//调用logTransaction  
  7.     }  
  8.     void logTransaction(logInfo) const;//声明为非virtual  
  9. };  
  10.   
  11. class BuyTransaction:public Transaction  
  12. {  
  13. public:  
  14.     BuyTransaction(parameters):Transaction(parameters){}//每个BuyTransaction都有专属自己的logTransaction信息  
  15. };  

关于拷贝赋值操作符,有两个编程好习惯

  • 令operator=返回一个reference to *this
  • 在operator=中处理“自我赋值”
[cpp]  
  1. int x,y,z;  
  2. x=y=z=1;  

上面的代码能通过编译并且表现得完全符合我们的预期。为了使我们的代码和C++规范保持一致,我们也应该令operator=返回一个对*this的引用。当然你不这样做编译器也不会报错,只是这是一个共同遵守的约定。

某些情况下,会出现一种令我们意想不到的情况:对象自我赋值,如果你够聪明,你当然不会写出这样的代码,但是你不能保证使用你的代码的人也像你一样聪明,所以,这种情况应该被我们考虑到。

看看一个不安全的operator=的实现,看看会发生什么:

[cpp]  
  1. Widget& Widget::operator=(cosnt Widget& rhs)//widget类中包含了一个Bitmap类的成员pb  
  2. {  
  3.     delete pb;  
  4.     pb= new Bitmap(*rhs.pb);  
  5.     return *this;  
  6. }  

上面的代码一般人当然会这么实现,但是看看当被自我赋值的时候会发生什么。它先删除了自己的pb对象,然后又将pb赋值给它自己,但是我们知道它已经被删除了,这是很明显的一个错误。

一个很好的解决方法是,以下的实现方式:

[cpp]  
  1. Widget& Widget::operator(cosnt Widget& rhs)  
  2. {  
  3.     Bitmap* pOrig = pb;  
  4.     pb = new Bitmap(*ths.pb);  
  5.     delete pOrig;  
  6.     return *this;  
  7. }  

拷贝函数应该确保复制对象内的所有成员变量及所有基类成分,因此你应该为派生类撰写拷贝构造函数,因为如果不这样做,派生类的default构造函数会对基类进行default构造,也就是说,派生类的基类部分没有被拷贝,这显然是不符合我们期望的。所以,你应该为派生类撰写拷贝函数。