一个虚析构函数引发的讨论
2016-06-12 15:11
399 查看
今天同事帮我作code review的时候,提了一个comment: 为什么这个类的析构函数前面不加上virtual, 防止多态析构的时候父类析构函数没有被调到?
我解释说是因为这个类没有其他的虚函数,也没打算让其他类来继承它,如果非要在析构函数前面加个virtual, 这个类还要维护一个虚函数表指针,有点画蛇添足。
接着同事的回答也很有道理:万一这个类被继承了,又刚好是用父类指针的方式析构呢?
我当时的第一反应是:那不是所有C++的类都要加个析构函数了? 后来反应过来,能不能利用一些手段,把这个类限制住,不允许它被继承?
在JAVA中有一个关键字:final, 被final修饰的类或方法不能被用来继承。那C++呢?查了下资料,原来在C++11中,也引入了final关键字,来达到同样的目地。
但是我们这个项目是运行在老版本的C++上的,这又怎么办?
我们知道,派生类对象实例化时,首先要调用基类构造函数,对基类部分对象进行初始化,之后再调用派生类构造函数,对派生类部分的对象进行初始化。那我们能不能像单件那样,对基类的构造函数动一些手脚,来防止它被派生类调用?
后来果然在Stackoverflow上找到了下面的一种方法:
在上面的代码中,class Derive试图继承class Base时,会报编译错误:
error: ‘CSealed::CSealed()’ is protected
从而我们达到了禁止class Base被继承的目的。
为什么Derive会因为CSealed的原因,不能继承Base呢?因为这里利用的虚继承的机制,使得Derive必须要跨过Base,直接去调用CSealed的构造函数,但是又因为CSealed的构造函数是protected的,Derive没有调用权限,从而导致了编译出错。
又由于Base是继承自CSealed的,因此Base对CSealed的构造函数有访问权限。
最后总结一下,这个问题本身就比较tricky。其根源是C++在多态情况下,对类对象进行析构时,不会调用其父类的非虚析构函数陷阱。为了避免踩进这个陷阱,我们又做了一些workaround来在编译器的范围限制它,这也难怪在C++11中会引入final关键字了。
我解释说是因为这个类没有其他的虚函数,也没打算让其他类来继承它,如果非要在析构函数前面加个virtual, 这个类还要维护一个虚函数表指针,有点画蛇添足。
接着同事的回答也很有道理:万一这个类被继承了,又刚好是用父类指针的方式析构呢?
我当时的第一反应是:那不是所有C++的类都要加个析构函数了? 后来反应过来,能不能利用一些手段,把这个类限制住,不允许它被继承?
在JAVA中有一个关键字:final, 被final修饰的类或方法不能被用来继承。那C++呢?查了下资料,原来在C++11中,也引入了final关键字,来达到同样的目地。
但是我们这个项目是运行在老版本的C++上的,这又怎么办?
我们知道,派生类对象实例化时,首先要调用基类构造函数,对基类部分对象进行初始化,之后再调用派生类构造函数,对派生类部分的对象进行初始化。那我们能不能像单件那样,对基类的构造函数动一些手脚,来防止它被派生类调用?
后来果然在Stackoverflow上找到了下面的一种方法:
class CSealed { protected: CSealed() {} }; class Base : virtual CSealed { public: Base() {} ~Base() {cout << "Base::~Base()" << endl;} }; class Derive : public Base { public: Derive() {cout << "derive" << endl;} };
在上面的代码中,class Derive试图继承class Base时,会报编译错误:
error: ‘CSealed::CSealed()’ is protected
从而我们达到了禁止class Base被继承的目的。
为什么Derive会因为CSealed的原因,不能继承Base呢?因为这里利用的虚继承的机制,使得Derive必须要跨过Base,直接去调用CSealed的构造函数,但是又因为CSealed的构造函数是protected的,Derive没有调用权限,从而导致了编译出错。
又由于Base是继承自CSealed的,因此Base对CSealed的构造函数有访问权限。
最后总结一下,这个问题本身就比较tricky。其根源是C++在多态情况下,对类对象进行析构时,不会调用其父类的非虚析构函数陷阱。为了避免踩进这个陷阱,我们又做了一些workaround来在编译器的范围限制它,这也难怪在C++11中会引入final关键字了。
相关文章推荐
- 使用C++实现JNI接口需要注意的事项
- 关于指针的一些事情
- c++ primer 第五版 笔记前言
- share_ptr的几个注意点
- Lua中调用C++函数示例
- Lua教程(一):在C++中嵌入Lua脚本
- Lua教程(二):C++和Lua相互传递数据示例
- C++联合体转换成C#结构的实现方法
- C++高级程序员成长之路
- C++编写简单的打靶游戏
- C++ 自定义控件的移植问题
- C++变位词问题分析
- C/C++数据对齐详细解析
- C++基于栈实现铁轨问题
- C++中引用的使用总结
- 使用Lua来扩展C++程序的方法
- C++中调用Lua函数实例
- Lua和C++的通信流程代码实例
- C与C++之间相互调用实例方法讲解
- 解析C++中派生的概念以及派生类成员的访问属性