基类的析构函数需不需要写成虚函数
2011-11-05 01:10
190 查看
我之前一直认为,基类的析构函数应当是虚函数,否则会留下析构时的隐患,并且在没有其他虚函数的时候,dynamic_cast将不能工作。
举个例子,如下,基类Base仅仅提供一个唯一的ID来标识一个实例化的对象,它没有其他任何使用虚函数的需求。
首先,一个隐患:
会输出什么呢?
而DerivedAObj的析构函数并没有调用。假设该类的析构函数实际上对一些堆分配的内存进行了清理工作,那么这种隐患就会造成内存泄漏。
其次假设这个时候,我有个方法:
这个时候编译会有错误提示:error C2683: 'dynamic_cast' : 'DerivedA' is not a polymorphic type
这个错误是说,DerivedA不是一个多态类型,无法使用dynamic_cast进行动态转换。我们知道,dynamic_cast的动态转换依靠的是RTTI,该信息是存在于虚表中。但这里继承中没有使用任何虚函数,因此无法在运行时得到类型信息。
那么,是不是一定要给基类,比如Base的析构函数加上virtual呢?
如果Base::~Base()是虚函数的话,那么首先当子类对象的指针转换为基类指针后再删除时,依然会正确的进行析构操作。
比如上面的输出就会变成:
其次,dynamic_cast也会正确操作。
这似乎都是成为基类一定要使用虚析构函数的理由,但是,同样有一些其他的理由试图说明,没有必要为没有需求的基类设置虚析构函数。
比如Base,它仅仅提供了一个ClassID,如果声明虚析构函数,则标志着所有继承于它的子类都将有着自己的虚表,特别的,继承于它的DerivedB可能仅仅就是一个数据类,但它仍然要建立自己的虚表,这是显然都会降低性能。
而多态的需求,可能并不从继承链的顶端开始,比如对于Base的子类DerivedA,从这一层开始,可能会使用到多态的特性,那么,完全可以从这一节点声明虚析构函数。而不是Base类。因为继承于Base类的其他子类(如DerivedB)完全没有必要消耗性能去建立一张虚表。
既然这样,为了解决正确析构的问题,我们声明DerivedA::~DerivedA()为虚析构函数
则
会正确输出:
DerivedAObj::~DerivedAObj()
DerivedA::~DerivedA()
Base::~Base()
那么,如果这样使用呢?
遗憾的是,这只会输出:
Base::~Base()
为什么?因为~Base()不是虚函数。
纠结么?其实不纠结。如果从性能上考虑,的确不需要Base使用虚析构函数,那么就应该将Base的析构函数定义为private,以防止对Base进行delete操作,如果使用者尝试这样做,编译器将会报错,强制使用者正确的使用对象,防范隐患的产生。
还有问题,dynamic_cast呢?
事实上,dynamic_cast依赖于RTTI,而RTTI并不是在所有的情况下都是开启的,所以本来安全的转换其实并不一定安全。假如你写的是个底层库,你不知道它会用在什么地方,那个地方是否开启或是否支持RTTI,因此,更安全的方法是自己实现一套RTTI的机制。
一般来说,默认开启RTTI,dynamic_cast依赖于虚表中的RTTI信息,如果没有定义虚函数,编译器会报错。并且dynamic_cast在菱形/cross继承等方面还存在着问题,所以说,dynamic_cast是“不一定安全”的转换。
结论:
当然,这是见仁见智的看法,通常情况下,我们还是大量使用到动态转换,但在写一些特定的组件或底层时,应当慎重考虑RTTI的实现问题。
对于基类是否定义虚析构函数的问题,在不关注性能的情况下,定义虚析构函数可以避免delete的隐患。同样,关注性能的情况下,在未定义虚析构函数的层级,就应该注意将析构函数私有化(private),防止造成隐藏的bug。
举个例子,如下,基类Base仅仅提供一个唯一的ID来标识一个实例化的对象,它没有其他任何使用虚函数的需求。
typedef long ClassID; ClassID gID; class Base { public: Base() { mClassID = gID++; } ~Base() { printf("Base::~Base()\n"); } public: ClassID getClassID() { return mClassID; } private: ClassID mClassID; }; class DerivedA : public Base { public: DerivedA() { mX = mY = 0; } ~DerivedA() { printf("DerivedA::~DerivedA()\n"); } public: int getX() { return mX; } void setX( int x ) { mX = x; } int getY() { return mY; } void setY( int y ) { mY = y; } private: int mX; int mY; }; class DerivedAObj : public DerivedA { public: DerivedAObj() { mRaduis = 0; } ~DerivedAObj() { printf("DerivedAObj::~DerivedAObj()\n"); } public: int getRaduis() { return mRaduis; } void setRaduis( int raduis ) { mRaduis = raduis; } private: int mRaduis; };
首先,一个隐患:
DerivedAObj* pAObj = new DerivedAObj; DerivedA* pA = pAObj; delete pA;
会输出什么呢?
DerivedA::~DerivedA() Base::~Base() |
其次假设这个时候,我有个方法:
void printRaduis( DerivedA* pObj ) { DerivedAObj* pAObj = dynamic_cast< DerivedAObj* >( pObj ); if( pAObj != NULL ) { printf( "Raduis: %d\n", pAObj->getRaduis() ); } }
这个时候编译会有错误提示:error C2683: 'dynamic_cast' : 'DerivedA' is not a polymorphic type
这个错误是说,DerivedA不是一个多态类型,无法使用dynamic_cast进行动态转换。我们知道,dynamic_cast的动态转换依靠的是RTTI,该信息是存在于虚表中。但这里继承中没有使用任何虚函数,因此无法在运行时得到类型信息。
那么,是不是一定要给基类,比如Base的析构函数加上virtual呢?
如果Base::~Base()是虚函数的话,那么首先当子类对象的指针转换为基类指针后再删除时,依然会正确的进行析构操作。
比如上面的输出就会变成:
DerivedAObj::~DerivedAObj() DerivedA::~DerivedA() Base::~Base() |
这似乎都是成为基类一定要使用虚析构函数的理由,但是,同样有一些其他的理由试图说明,没有必要为没有需求的基类设置虚析构函数。
class DerivedB : public Base { public: DerivedB() { } ~DerivedB() { } //Data block };
比如Base,它仅仅提供了一个ClassID,如果声明虚析构函数,则标志着所有继承于它的子类都将有着自己的虚表,特别的,继承于它的DerivedB可能仅仅就是一个数据类,但它仍然要建立自己的虚表,这是显然都会降低性能。
而多态的需求,可能并不从继承链的顶端开始,比如对于Base的子类DerivedA,从这一层开始,可能会使用到多态的特性,那么,完全可以从这一节点声明虚析构函数。而不是Base类。因为继承于Base类的其他子类(如DerivedB)完全没有必要消耗性能去建立一张虚表。
既然这样,为了解决正确析构的问题,我们声明DerivedA::~DerivedA()为虚析构函数
virtual ~DerivedA() { printf("DerivedA::~DerivedA()\n"); }
则
DerivedAObj* pAObj = new DerivedAObj; DerivedA* pA = pAObj; delete pA;
会正确输出:
DerivedAObj::~DerivedAObj()
DerivedA::~DerivedA()
Base::~Base()
那么,如果这样使用呢?
DerivedAObj* pAObj = new DerivedAObj; Base* pB = pAObj; delete pB;
遗憾的是,这只会输出:
Base::~Base()
为什么?因为~Base()不是虚函数。
纠结么?其实不纠结。如果从性能上考虑,的确不需要Base使用虚析构函数,那么就应该将Base的析构函数定义为private,以防止对Base进行delete操作,如果使用者尝试这样做,编译器将会报错,强制使用者正确的使用对象,防范隐患的产生。
还有问题,dynamic_cast呢?
事实上,dynamic_cast依赖于RTTI,而RTTI并不是在所有的情况下都是开启的,所以本来安全的转换其实并不一定安全。假如你写的是个底层库,你不知道它会用在什么地方,那个地方是否开启或是否支持RTTI,因此,更安全的方法是自己实现一套RTTI的机制。
一般来说,默认开启RTTI,dynamic_cast依赖于虚表中的RTTI信息,如果没有定义虚函数,编译器会报错。并且dynamic_cast在菱形/cross继承等方面还存在着问题,所以说,dynamic_cast是“不一定安全”的转换。
结论:
当然,这是见仁见智的看法,通常情况下,我们还是大量使用到动态转换,但在写一些特定的组件或底层时,应当慎重考虑RTTI的实现问题。
对于基类是否定义虚析构函数的问题,在不关注性能的情况下,定义虚析构函数可以避免delete的隐患。同样,关注性能的情况下,在未定义虚析构函数的层级,就应该注意将析构函数私有化(private),防止造成隐藏的bug。
相关文章推荐
- 只有当一个类被用来作为基类的时候,才把析构函数写成虚函数.
- (C++)基类的析构函数为虚函数的优点
- 基类析构函数必须为虚函数否则会造成内存泄漏
- 多态基类的析构函数应该为虚函数
- 基类的析构函数为什么一般都是虚函数
- C/C++沉思-----多态时一定要将父类(基类)的析构函数定义为虚函数
- 【C++】基类析构函数为什么要为虚函数
- C++ 为什么设置基类的析构函数为虚函数
- 基类析构函数设置成虚函数意义深远
- C++中将基类的析构函数定义为虚函数
- 为什么基类的析构函数要声明成虚函数
- C/C++沉思-----多态时一定要将父类(基类)的析构函数定义为虚函数
- 基类析构函数为虚函数的作用
- 为什么基类的析构函数必须为虚函数的原因-个人理解
- C++ 为什么设置基类的析构函数为虚函数
- C++:基类析构函数为虚函数场景
- 只有基类的析构函数需要为虚函数
- 基类的析构函数是虚函数
- C++ 设置基类的析构函数为虚函数
- 为什么基类的析构函数是虚函数