为什么构造函数不能是虚函数而析构函数可以
2016-04-07 12:17
519 查看
首先,虚函数的实现原理是:在定义具有虚函数的类或者继承类的继承的时候,会相应建立一个虚函数表vtable,即每个类都对应一个需函数表,而在定义类的对象的时候,每个对象都会有一个指向相应类的虚表指针vptr,vptr指向虚表的入口地址,在调用相应的虚函数的时候,根据该入口地址寻找对应的函数。 对于构造函数,其作用是在对象实例化的时候自动调用,对该对象进行初始化操作。前述中提到,虚函数是通过vptr来调用的,而调用构造函数的时候实例化并未完成,也就是说此时并不存在vptr,因而,无法使用vptr来调用构造函数。 另一方面,虚函数的调用是虚调用,通过在运行时查询虚函数表得到具体函数入口地址,相当于只需要有部分信息就可以调用该函数。然而定义具体类的对象的时候,需要明确指定对象类型,而且在定义子类对象的时候首先调用的是父类的构造函数然后才是调用子类构造函数,如果使用了虚函数,那么仅仅是调用子类构造函数并不能完成对象的初始化。 而对于析构函数,则需要定义为虚析构函数,防止内存泄露的发生。 如下代码:
#include <iostream> class A{ public: A(){ std::cout<<"construct A!"<<std::endl; }; ~A(){ std::cout<<"A has been destructed!"<<std::endl; } }; class B:public A{ public: B(){ std::cout<<"construct B!"<<std::endl; }; ~B(){ std::cout<<"B has been destructed!"<<std::endl; } }; int main() { B* ptrB=new B; delete ptrB; } 此时的输出为: construct A construct B B has been destructed! A has been destructed!
也就是对于普通的定义子类对象然后析构该对象的行为,其首先是调用子类的析构函数,然后再调用父类的析构函数,因为在定义子类对象的时候,首先是调用了父类对象的构造函数,然后才是调用子类的构造函数,那么子类对象中包含了父类的信息,析构的时候也必然需要调用父类的析构函数。
而如果使用父类的指针指向子类的对象:
int main() { A* ptrAB=new B; delete ptrAB; } 此时的输出是 construct A construct B A has been constructe!
此时仅仅是调用了父类的析构函数,因为指针类型是父类的。此时会造成的后果是,保存子类信息的那部分内存空间没有被析构,导致内存泄露。
如果将析构函数定义成虚函数,那么输出的时候则和普通定义的子类的对象析构一样,输出为:
construct A construct B B has been destructed! A has been destructed!
此时的析构行为才是正确的,即先调用子类析构函数,再调用父类析构函数。
相关文章推荐
- c#中虚函数的相关使用方法
- 详解C#编程中构造函数的使用
- C++中拷贝构造函数的应用详解
- C++虚函数及虚函数表简析
- C++之普通成员函数、虚函数以及纯虚函数的区别与用法要点
- 完全掌握C++编程中构造函数使用的超级学习教程
- 详解C++中如何将构造函数或析构函数的访问权限定为private
- 构造函数不能声明为虚函数的原因及分析
- C++虚函数表实例分析
- 深入讲解C++中的构造函数
- C++类成员构造函数和析构函数顺序示例详细讲解
- C++虚函数的实现机制分析
- C++中虚函数与纯虚函数的用法
- c++基础语法:构造函数与析构函数
- JavaScript 构造函数 面相对象学习必备知识
- JavaScript面向对象设计二 构造函数模式
- Javascript面向对象编程(二) 构造函数的继承
- 成员初始化列表与构造函数体中的区别详细解析
- JavaScript精炼之构造函数 Constructor及Constructor属性详解
- 虚函数中的继承关系