1. C++虚成员函数概述
C++中的虚成员函数是为了实现多态而设计的,是需要在基类中使用virtual关键字来声明。子类继承基类后,如果子类中定义了同名函数,那么这个函数也会变成虚函数。
虚函数通过调用对象的实际类型而不是指针或引用类型来实现多态。这意味着如果一个指向子类对象的指针被赋值为一个指向基类对象的指针,那么当这个指针调用一个虚函数时,将会根据实际指向的对象类型调用相应的函数。
class Parent {
public:
virtual void Print() {
printf("I am parent\n");
}
};
class Child: public Parent {
public:
void Print() {
printf("I am child\n");
}
};
在上面的代码中,Parent类中的Print()函数在声明时使用了virtual关键字。而Child类继承Parent类后重载了Print()函数。
2. 动态联编的实现
2.1 虚函数表
在C++中,每个对象中都会有一个指向虚函数表(VFTable)的指针,这个指针指向一个数组,里面存放着所有虚函数的地址。这个指针被称为虚基类指针(VPTR),当调用虚函数时,程序会通过虚基类指针找到虚函数表,然后调用相应的函数。
虚函数表是编译器生成并隐藏在类的内部的。虚函数表是在编译期就已经建立好了,其结构如下:
class Parent {
public:
virtual void Print() {
printf("I am parent\n");
}
};
class Child: public Parent {
public:
void Print() {
printf("I am child\n");
}
};
/*这里的虚函数表结构如下:
* +---------------+
* | Parent::Print |
* +---------------+
*/
如上,虚函数表中存放的是虚函数的地址。
2.2 实现原理
调用虚函数时,程序会通过VPTR找到对象中的虚函数表,然后再通过函数在虚函数表中的索引找到函数的地址。这个过程就是通过虚函数表实现动态联编的过程。
这里以一个例子来说明:
Parent* p = new Child();
p->Print();
在上面的代码中,Parent指针p指向了一个Child对象。而调用Print()函数时,程序会先通过VPTR获取到对象的虚函数表指针,然后在虚函数表中查找Print()函数的地址。由于Child类重载了Print()函数,所以查找到的将是Child类中Print()函数的地址。
2.3 虚析构函数
在使用继承的情况下,如果父类的析构函数不是虚函数,那么在delete 虚基类指针所指向的子类对象时,只会把父类自己所占用的内存释放掉,而不会释放子类中新加入的成员变量所占用的内存。如果同时有多个派生类指向同一个基类的实例,那么使用delete操作符来删除这个对象时,只会调用基类的析构函数而忽略其它派生类,这样会导致派生类中可能存在的其它资源无法被释放,从而产生内存泄漏的情况。
为了避免这种情况的发生,应该在基类中将析构函数声明为虚函数。
3. 总结
C++中的虚函数机制是实现多态的重要手段,它通过动态联编的方式实现了在运行时才确定函数调用的方式。同时,在使用继承的情况下,需要注意将析构函数声明为虚函数,以避免内存泄漏的情况。
本文从C++虚成员函数和动态联编两个方面进行了详细的介绍,并通过代码示例来说明虚函数表的实现和动态联编的过程。相信读者已经对C++虚成员函数和动态联编有了更深刻的理解。