virtual 理解学习整理

基类中的虚函数允许派生类重写功能,编译器会保证派生类对象使用的是自己重写的功能,即使对象是通过基类指针访问的,例如前文中的 func(Animal *xyz) 函数,func(cat) 输出的实际上是 Cat 类重写的功能。这是一个非常有用的特性,调用者甚至都不需要知道 Cat 等派生类的实现,因为只需使用基类 Animal 指针就能够轻易的调用所有派生类的重写功能。

基类的虚函数可以完全被重写,也可以部分的被重写,所谓的“部分被重写”,其实就是派生类在重写基类虚函数时,也可以调用基类虚函数的功能。

虚函数和常规函数被调用时有什么不同?

常规的非虚函数是静态解析的,即在编译时即可根据指针指向的对象确定是否被调用,例如文章开头的例子,如果 eat() 函数是非虚函数:

// eat() 函数不是虚函数

Animal *animal = new Animal;

Cat *cat = new Cat;

animal->eat();

// 输出: "I'm eating generic food."

cat->eat();

// 输出: "I'm eating a rat."

此时编译器在编译时就能确定 animal->eat() 调用的是 Animal::eat() 函数,cat->eat() 调用的是 Cat::eat() 函数。在 func(Animal *xyz) 函数中,因为其形参是 Animal *指针类型,所以即使传入的是 cat 对象指针,在 func() 函数内部也会被强制转换为Animal *指针,因此 func(cat) 调用的仍然是 Animal::eat() 函数。

而虚函数就不同了,它是动态解析的,也即在程序被编译后,运行时才根据对象的类型,而不是指向对象的指针类型决定其是否被调用,这就是说为的“动态绑定”。

关于“动态绑定”,我们在下一节中再做详细讨论,这里先留个印象。

在C++语言中,如果某个类有虚函数,那么大多数编译器都会自动的为其对象维护一个隐藏的“虚指针(virtul-pointer)”,虚指针指向一个全局“虚表(virtual-table)”,虚表中存放若干函数指针,这些函数指针指向类中的虚函数。请看下面这段C++语言代码:

class A {

public:

virtual void vfoo1();

virtual void vfoo2();

void foo1();

void foo2();

private:

int prv_i1, prv_i2;

};

显然,类 A 有两个常规函数以及两个 int 型的成员变量,此外,它还有两个虚函数,因此编译器会创建一个虚表,虚表中存放的是函数指针,它们分别指向类 A 的虚函数,如下图所示:

 这里应注意,虚表是属于类的,而不是对象的,也就是说,即使有成千上万个 A 对象,虚表也仅有一个,这些对象共用一个类虚表。编译器会自动的为每个对象创建一个隐藏的“虚指针”__vptr,它指向类 A 的虚表,(当类的对象在创建时便拥有了这个指针,且这个指针的值会自动被设置为指向类的虚表。)如下图所示:

 C++语言这样实现虚函数机制的空间开销是微乎其微的,事实上,每一个对象只需要一个额外的“虚指针”__vptr就能够调用类的虚函数。同样的,时间开销也很小:相比于常规函数的调用,虚函数的调用只不过多出了额外的两个步骤:

获取虚表指针,得到虚表从虚表中取出虚函数的地址读者应注意,这里的讨论为了突出主题,理想化了一些情况。

为什么类成员函数默认都不是虚函数?

既然虚函数这么好用,那为什么C++语言不把类的成员函数默认定义为虚函数呢?

其实仔细考虑一下,虚函数的“好用”主要体现在定义在基类中实现多态上,但并不是所有的类都需要被设计为基类,一昧的使用虚函数可能会造成语义上的歧义,隐藏程序员的设计。仅将需要被继承的基类中需要被重写的函数定义为虚函数,要比将所有函数定义为虚函数清晰多了。此外,经过前面的讨论,我们也知道虚函数的效率实际上是没有常规函数高的,同样的功能中,仅从被调用过程来看,它的时间开销和空间开销都比常规函数高,每个对象还需要额外的虚指针索引虚表。

 “对象的虚表指针用来指向自己所属类的虚表,虚表中的指针会指向其继承的最近的一个类的虚函数”这个特点,便可以快速将这几个类的对象模型在自己的脑海中描绘出来。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值