纯虚函数的默认实现

纯虚函数的默认实现

分类: C++ 设计与模式   1720人阅读  评论(5)  收藏  举报
class 审查 工作
在帮新同事进行代码审查的时候,常常会发现这样的问题:代码中原有基类B和派生类D1,现在新加一个派生类D2,它有一个函数f2()。由于经验不足,新同事并没有注意到D1也有类似的函数f1()。于是造成了类似的代码出现在了两个地方,代码冗余造成将来的维护工作异常困难。注意到f()实际上是一个通用的行为,我们可以把它抽出来放到基类中,如下所示。

  1. class B  
  2. {  
  3. public:  
  4.   virtual void f();  
  5. };  
  6.   
  7. void B::f()  
  8. {  
  9.   //f默认的实现方式..  
  10. }  
  11.   
  12. class D1: public B {...};  
  13. class D2: public B {...};  

这里有两点。基类里面的f()有函数定义是为了避免代码冗余,因为D1和D2关于f的实现都是相同的。f()是虚函数是考虑到将来万一有一个派生类需要f()有不同的实现方式时,它可以重新定义f。

到目前为止一切顺利。好了,假设现在另一个同事又加了一个派生类D3,它要求f()有不同的实现方式。但是,他忘记了重新定义f。。这是一个灾难。

  1. class D3: public B {...};  
  2. B* b = new D3;  
  3. // f做的动作不是我希望的!  
  4. b->f();  
注意,这个问题的本质并不是说基类不能有缺省的实现,而是说如果派生类需要使用基类缺省的实现,必须显式地表示出来。所以,我们需要一个能够强制派生类定义f的方式,答案就是纯虚函数。
  1. class B  
  2. {  
  3. public:  
  4.   virtual void f() = 0;  
  5. };  
  6.   
  7. class D3: public B {...};  
  8. B* b = new D3; // 编译报错:抽象类无法实例化  
  9. b->f();  
我们知道有纯虚函数的类是抽象类,而抽象类是无法实例化的。如果D3没有实现f,那么D3也无法实例化。这就要求每个要实例化的子类都必须显示地实现纯虚函数f。另一方面,f的缺省实现放哪里?

一种方法是把缺省实现作为另一个非虚函数放在基类里,当然它是保护的,这样外界无妨直接访问而派生类可以通过调用该函数来达到缺省的行为。

  1. class B  
  2. {  
  3. public:  
  4.   virtual void f() = 0;  
  5. protected:  
  6.   void f_impl();  
  7. };  
  8.   
  9. void B::f_impl()  
  10. {  
  11.   // 缺省实现..  
  12. }  
  13.   
  14. // D2类似  
  15. class D1: public B  
  16. {  
  17. public:  
  18.   virtual void f()  
  19. };  
  20.   
  21. void D1::f()  
  22. {  
  23.   // 对于缺省实现已经够用的派生类来说,调用基类的缺省实现就可以了  
  24.   f_impl();  
  25. }  
  26.   
  27. class D3: public B  
  28. {  
  29. public:  
  30.   virtual void f()  
  31. };  
  32.   
  33. void D3::f()  
  34. {  
  35.   // 特殊实现..  
  36. }  
第二种方法是把缺省的实现放入基类纯虚函数的函数体。没错,纯虚函数可以有函数定义存在,它的作用就是提供虚函数缺省的实现方式。和一般虚函数不同,派生类必须重新定义纯虚函数。如果需要使用缺省实现,也必须显式地调用基类的相关函数。
  1. class B  
  2. {  
  3. public:  
  4.   virtual void f()  
  5. };  
  6.   
  7. void B::f()  
  8. {  
  9.   // 缺省实现..  
  10. }  
  11.   
  12. // D2类似  
  13. class D1: public B  
  14. {  
  15. public:  
  16.   virtual void f()  
  17. };  
  18.   
  19. void D1::f()  
  20. {  
  21.   // 显式调用基类的缺省实现  
  22.   B::f();  
  23. }  
这种方法的好处是少了一个需要维护的函数,缺点是客户代码可以直接调用基类的缺省实现。
  1. B* b = new D1;  
  2. b->B::f();  

小结:

  1. 普通的虚函数提供了接口与缺省实现。(公有继承的)派生类继承接口的同时可以自动继承接口的缺省实现(免费的午餐),或者选择重新定义该函数来特化自己的行为。风险是以后需要定制此行为的派生类可能因为忘记重定义而错误地使用缺省实现。
  2. 纯虚函数只提供了接口。但是具有函数体的纯虚函数同时还提供了缺省实现。派生类必须显式定义接口。可以通过显式调用基类函数来完成缺省行为。
  3. 为了完整,顺便补充一下,公有非虚函数提供了接口与强制实现。也就是说所有的派生类必须继承这个接口及其实现。

你可能感兴趣的:(C++,设计与模式)