1 虚函数表与 vptr 指针
如果类中有虚函数,那么在使用该类定义对象的时候,会创建一个虚函数表,虚函数表存放了虚函数的入口地址,而 vptr 指向这个虚函数表,父类的vptr指针指向父类的虚函数表,子类的vptr指针指向子类的虚函数表(虚函数的入口地址都在这个虚函数表中)。动态联编就是这么实现的,根据vptr指针决定调用哪个虚函数,当遇到虚函数调用时,会根据vptr指针找到虚函数表,并在虚函数表中查找函数原型。
vptr指针初始化是分步实现的
1.使用子类定义一个子类对象,会初始化子类的vptr指针 2.当父类的构造函数执行时,子类的 vptr 指针会指向父类的虚函数表 3.当父类的构造函数执行完毕时,子类的 vptr 指针指向子类的虚函数表
通过一个例子可以证明vptr指针的分步初始化
#include <iostream>
using namespace std;
class Class1
{
public:
Class1()
{
print_func();
cout << "class 1 构造" << endl;
}
virtual void print_func()
{
cout << "class 1" << endl;
}
};
class Class2 : public Class1
{
public:
Class2()
{
print_func();
cout << "class 2 构造" << endl;
}
virtual void print_func()
{
cout << "class 2" << endl;
}
};
int main()
{
Class2 c2;
system("pause");
return 0;
}
编译运行可以看到,在Class1构造函数中调用了Class1的print_func函数,这说明此时vptr指针指向了Class1的虚函数表;在Class2构造函数中调用了Class2的print_func函数,说明此时vptr指针指向了Class2的虚函数表。
2 虚析构函数
虚析构函数是指,在父类析构函数加 virtual 关键字,通过父类指针,执行所有子类的析构函数,释放所有子类内存。
应用场景:一个函数的参数是基类指针,在主调函数 new 分配了内存,需要在被调函数中使用完该对象后 delete 释放内存,我在调用这个函数时传入了一个派生类对象,如果基类中析构函数没有加 virtual 关键字,就会静态联编,delete 时只执行基类的析构函数,而没有执行派生类析构函数,造成了派生类对象中内存泄漏。虚析构函数会执行动态联编,把基类和派生类的析构函数都执行一遍。
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
class MyClassA
{
public:
MyClassA(const char* str)
{
this->pA = new char[strlen(str) + 1];
strcpy(this->pA, str);
cout << "A 构造函数" << endl;
}
//~MyClassA()
virtual ~MyClassA()//虚析构函数
{
if (this->pA != NULL)
{
delete[] this->pA;
}
this->pA = NULL;
cout << "A 析构函数 " << endl;
}
private:
char* pA;
};
class MyClassB : public MyClassA
{
public:
MyClassB(const char* str) : MyClassA(str)
{
this->pB = new char[strlen(str) + 1];
strcpy(this->pB, str);
cout << "B 构造函数" << endl;
}
~MyClassB()
{
if (this->pB != NULL)
{
delete[] this->pB;
}
this->pB = NULL;
cout << "B 析构函数 " << endl;
}
private:
char* pB;
};
class MyClassC : public MyClassB
{
public:
MyClassC(const char* str) : MyClassB(str)
{
this->pC = new char[strlen(str) + 1];
strcpy(this->pC, str);
cout << "C 构造函数" << endl;
}
~MyClassC()
{
if (this->pC != NULL)
{
delete[] this->pC;
}
this->pC = NULL;
cout << "C 析构函数 " << endl;
}
private:
char* pC;
};
void FuncTest(MyClassA* p)
{
delete p;
}
int main()
{
MyClassC* c = new MyClassC("hello C++");
FuncTest(c);
//delete c; //也会调用 C B A 的析构函数
system("pause");
return 0;
}
正常析构函数只会执行A(做函数参数的类)的析构函数,因为静态联编,编译器编译时并不知道具体要用哪个类的析构函数,所以只能选择函数参数的类的析构函数执行
将基类的析构函数加 virtual 关键字,即虚析构函数,将会依次调用 C B A(基类和所有派生类)的析构函数;动态联编,在执行时根据指针具体传入哪个类的对象决定如何调用