构造函数调用虚函数

Posted 小马识图

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了构造函数调用虚函数相关的知识,希望对你有一定的参考价值。

今天看android canvas 类的源代码看到 Canvas的构造函数之一:

public class Canvas
...
    public Canvas() 
        if (!isHardwareAccelerated()) 
            ...
         else 
            ...
        
    

    public boolean isHardwareAccelerated()
        return false;
    


public abstract class HardwareCanvas extends Canvas 
    @Override
    public boolean isHardwareAccelerated() 
        return true;
    
    ...
    

很明显,Canvas 构造函数调用的isHardwareAccelerated() 是希望子类重载用的。回想以前写C++代码的时候好像很少碰到在构造函数里面调用虚函数的情形,对于C++如何解决类似的问题似乎有些遗忘了,于是Google了一下,发现这里面还是有一些文章,于是摘抄下来,以备不时之需。


注意:

Calling virtual functions from a constructor or destructor is dangerous and should be avoided whenever possible.

在C++中通过构造函数或者析构函数调用虚函数是危险行为,应该尽量避免。


C++现实情形

C++对于构造函数中调用虚函数是支持的,但是需要注意的是当在构造函数中调用虚函数的时候对多态的支持尚未完成。对于上述Canvas 中调用 isHardwareAccelerated() 的情形, 在C++中是不可能调用到 HardwareCanvas::isHardwareAccelerated() 的。

C++中虚函数表会先于构造函数的调用被建立, 当 Canvas 调用虚函数 isHardwareAccelerated() 的时候, 此时HardwareCanvas 子类的任何信息包括虚表还没有建立起来,因此这时候调用的是 Canvas 自己的 isHardwareAccelerated().


Java现实情形

Java采取了与C++的保守相比更激进的做法。在我们创建HardwareCanvas类的实例的时候,它会先把整个类层次结构建立起来,然后再调用基类构造函数和子类的构造函数,这样在Canvas类的构造函数调用的时候,多态就已经能够发挥作用了。这也是我们能够看到上面的代码的现实原因!


危险在哪里?

可以看到我们的例子中 isHardwareAccelerated() 仅仅简单的返回了 ture, false, 并没有访问 HardwareCanvas 的任何数据。 假如我们的 isHardwareAccelerated() 不小心访问了这些数据,那很显然在 HardwareCanvas 构造函数没有调用之前,这些数据仅仅是默认值,有可能是 null, 这就给我们隐含了未知的风险,有可能导致运行时的崩溃。


C++的解决方法

C++中的解决方法其实很简单,简单到我们大多数人一直在用却不知道原因。

 class Canvas
 
     public:  
        typedef std::unique_ptr<Canvas> Ptr;      
        void init()
        
            ...
            if(!isHardwareAccelerated() )
            
               ...
            
            else
            
               ...
            
       
     protected:
        virtual bool isHardwareAccelerated() return false;

     public:
        static Ptr create()
        
             Ptr p( /*...use a factory to create a HardwareCanvas object via new...*/ );
             p->init();
             return p;
        


     protected:
         Canvas();
;

class HardwareAccelerated : public Canvas

protected:
    bool isHardwareAccelerated()  return true; 
;

基本思想就是 把 构造函数中需要调用虚函数的部分分离出来并放到一个 init 函数中。
这样我们可以保证 init 被调用的时候 HardwareCanvas 构造函数已经被创建, 此时访问它的成员就安全了。


还有一种方法适用于虚函数不需要访问子类的成员的时候,比如我们的Canvas 类的 isHardwareAccelerated().

  • 将需要多态支持的部分抽象成一个基类或者接口
class Helper

    public:
    virtual bool isHardwareAccelerated() return false;
;
  • 继承基类或实现接口
class Helper_A : public Helper

    public:
    virtual bool isHardwareAccelerated()  return true;
;
  • 基类构造函数接受一个Helper类的引用:
class Canvas

    public:
    Canvas( Helper& );
;
  • 子类的构造函数传入自定义的Helper
class HardwareCanvas : public Canvas 

    public:
    HardwareCanvas( ) : Canvas( getHelper()) 

   private:
   static Helper_A& getHelper();
;

参考:

1. http://stackoverflow.com/questions/962132/calling-virtual-functions-inside-constructors.
2. https://isocpp.org/wiki/faq/strange-inheritance#calling-virtuals-from-ctor-idiom.

以上是关于构造函数调用虚函数的主要内容,如果未能解决你的问题,请参考以下文章

C++在构造函数中调用最终的虚函数

从基类构造函数调用纯虚函数

构造函数调用虚函数

构造函数调用虚函数

构造函数为什么不能为虚函数

为什么构造函数内部不能调用虚函数