今天看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++与Java构造函数中调用虚函数
本文探讨了C++与Java中构造函数调用虚函数的行为差异及潜在风险。C++中此类调用可能导致未预期的结果,而Java则能确保正确调用子类方法。此外,还介绍了C++中的两种解决方案。
955

被折叠的 条评论
为什么被折叠?



