虚函数的调用通过虚表指针来指定,虚表指针包含在对象的内存空间中,它在构造函数调用后完成初始化;
析构函数可以且常常是虚函数
这个原理上就很好理解啦,因为此时 vtable 已经初始化了,完全可以把析构函数放在虚函数表里面来调用。
C++类有继承时,析构函数必须为虚函数。如果不是虚函数,则使用时可能存在内存泄漏的问题。
虚表是全局唯一的,放在全局数据区;
空类,大小占用为1;
有虚函数,虚表指针占用为4;
非static数据成员的占用;
需要考虑内存对齐的问题;
默认构造函数;
析构函数;
赋值运算符重载;
拷贝构造函数;
基类指针可以指向派生类对象,其中句柄类型也就是指针类型决定哪个类的函数被调用,如果是基类指针指向派生类,那么调用的函数还是基类的;
但是,如果有了virtual以后,调用哪个版本的virtual函数则由句柄指向的对象来决定;也就是虽然指向的还是句柄,但是句柄仍然会指向派生类对象的虚表;
如下所示,使用基类D指向派生类E,在创建时调用E的构造函数,然后递归调用基类构造函数;在销毁时,调用D的析构函数,然后递归调用基类的析构函数;
- #include <iostream>
- using namespace std;
- // 基类和派生类的析构函数
- class C{
- public:
- C(){
- cout << "C constructor\n";
- }
- ~C(){
- cout << "C deconstructor\n";
- }
- };
-
- class D: public C{
- public:
- D(){
- cout << "D constructor\n";
- }
- ~D(){
- cout << "D deconstructor\n";
- }
- };
-
- class E:public D{
- public:
- E(){
- cout << "E constructor\n";
- }
- ~E(){
- cout << "E deconstructor\n";
- }
- };
-
- int main(){
- D* test = new E();
- delete test;
- return 0;
- }
-
- C constructor
- D constructor
- E constructor
- D deconstructor
- C deconstructor
如果析构函数是虚函数,则结果如下:
- #include <iostream>
- using namespace std;
- // 基类和派生类的析构函数
- class C{
- public:
- C(){
- cout << "C constructor\n";
- }
- virtual ~C(){
- cout << "C deconstructor\n";
- }
- };
-
- class D: public C{
- public:
- D(){
- cout << "D constructor\n";
- }
- ~D(){
- cout << "D deconstructor\n";
- }
- };
-
- class E:public D{
- public:
- E(){
- cout << "E constructor\n";
- }
- ~E(){
- cout << "E deconstructor\n";
- }
- };
-
- int main(){
- D* test = new E();
- delete test;
- return 0;
- }
-
- C constructor
- D constructor
- E constructor
- E deconstructor
- D deconstructor
- C deconstructor