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

2018-06-27 09:42:57来源:博客园 阅读 ()

新老客户大回馈,云服务器低至5折

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

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

[cpp] view plain copy
  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] view plain copy
  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] view plain copy
  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] view plain copy
  1. int x,y,z;  
  2. x=y=z=1;  

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

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

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

[cpp] view plain copy
  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] view plain copy
  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构造,也就是说,派生类的基类部分没有被拷贝,这显然是不符合我们期望的。所以,你应该为派生类撰写拷贝函数。

标签:

版权申明:本站文章部分自网络,如有侵权,请联系:west999com@outlook.com
特别注意:本站所有转载文章言论不代表本站观点,本站所提供的摄影照片,插画,设计作品,如需使用,请与原作者联系,版权归原作者所有

上一篇:洛谷P3804 【模板】后缀自动机

下一篇:leetcode笔记(四)9. Palindrome Number