说到虚函数,首先要讲一下OOP中的多态,多态简单的说就是一个接口,多种实现.
多态分为编译时多态和运行时多态。
编译时多态主要体现在函数重载;
运行时多态是指程序在运行时动态地识别对象,实现不同的行为,这是通过虚函数+继承实现的。
虚函数必须是类的非静态成员函数,也不能是构造函数, 访问权限为public
, 虽然在语法上设置成 private
和 protect
并没有错误,但虚函数的目的是为了实现多态,所以设置成 protect
和 private
是没有意义的。
静态成员函数不能是 virtual 函数的原因
- 静态成员函数可以不通过对象来调用,静态成员函数没有隐藏的this指针;virtual函数只能通过对象来调用,实现需要依靠隐藏的this指针
- 静态成员函数是在编译时就绑定了,而virtual函数在运行时才进行绑定
构造函数不能是 virtual 函数的原因
- 首先从设计理念上来说,构造函数不需要设置成 virtual 函数,从实现机制来说,构造函数也无法实现 virtual 函数
- 构造函数顾名思义就是构建一个对象,而虚函数是动态的识别对象的类型,这个对象属于是基类、派生类还是更深层次的类,这是运行在对象已经存在的基础上的。所以虚函数的调用必须在构造函数调用之后
- 虚函数的执行依赖于虚函数指针 vptr, 而 vptr 的初始化即让 vptr 指向 虚函数表 V-table 是在构造函数中实现的,所以
构造函数不能是虚函数
虚函数表 vtable 和虚函数表指针 vptr
C++的虚函数的实现机制依靠的就是 vtable 和 vptr。
vtable 实际上就是 virtual 函数的地址表,这张表解决了继承和覆盖的问题
例如:
class A {
public:
virtual void f();
virtual void g();
};
class B: public A{
public:
virtual void f();
virtual void h();
};
B::f() | A::g() | B::h() | 0 |
---|
上面表格则为B的 vtable, 最后一个值如果为1说明还有下一个虚函数表,存在于多重继承的情况下。
B类的内存分布的第一项即是 vptr,该指针指向B的 vtable
注意:
vtable 在编译时期确立, vptr 在运行期确立。
在继承中析构函数一定要被设置成虚函数
如果不设置成虚函数,那么当父类的指针调用子类的对象,该指针调用析构函数,将会调用父类的析构函数,而子类多出的那一部分数据内存将无法被释放。
网友评论