





















前言
本文主要记录一下对c++编程中虚函数virtual的理解。
虚函数virtual理解
virtual允许子类重新实现,并通过基类指针调用时能够动态选择正确函数,即运行时多态。虚函数或虚方法可以被子类继承和覆盖,编译期无法确定目标函数的具体指向。
如果基类的函数是“虚”的,则调用到的都是最终派生类(英语:most-derived class)中的函数实现,与指针或引用的类型无关。反之,如果函数非“虚”,调用到的函数就在编译期根据指针或者引用所指向的类型决定。
在C++中,在基类的成员函数声明前加上关键字virtual即可让该函数成为虚函数,派生类中对此函数的不同实现都会继承这一修饰符,允许后续派生类覆盖,达到迟绑定的效果。即便是基类中的成员函数调用虚函数,也会调用到派生类中的版本,即虚函数调用时,不看“调用代码写在哪个类”,而看“当前对象实际属于哪个类”。。
内部机制
c++实现虚函数通常依靠虚函数表vtable,vptr指针指向虚函数表vtable,每个类都有自己的vtable,vtable就是保存自己类中虚函数的地址。如果明确指定调用基类版本,则不会动态绑定。构造函数与析构函数不会向下调用,接口决定调用方式,对象决定执行版本。
定义虚函数的限制
(1)非类的成员函数不能定义为虚函数,类的成员函数中静态成员函数和构造函数也不能定义为虚函数,但可以将析构函数定义为虚函数。实际上,优秀的程序员常常把基类的析构函数定义为虚函数。因为,将基类的析构函数定义为虚函数后,当利用delete删除一个指向派生类定义的对象指针时,系统会调用相应的类的析构函数。而不将析构函数定义为虚函数时,只调用基类的析构函数。 (2)只需要在声明函数的类体中使用关键字“virtual”将函数声明为虚函数,而定义函数时不需要使用关键字“virtual”。 (3)当将基类中的某一成员函数声明为虚函数后,派生类中的同名函数(函数名相同、参数列表完全一致、返回值类型相关)自动成为虚函数。 (4)如果声明了某个成员函数为虚函数,则在该类中不能出现和这个成员函数同名并且返回值、参数个数、类型都相同的非虚函数。在以该类为基类的派生类中,也不能出现和这个成员函数同名并且返回值、参数个数、类型都相同的非虚函数。
性能影响
虚函数的性能开销主要源于其实现动态多态的机制,包括间接函数调用、内存开销、编译器优化受限、影响分支预测。调用虚函数需要通过虚表指针间接查表跳转,这带来了额外的指令开销和内存占用(每个包含虚函数的类需要维护虚函数表,每个对象需要存储虚表指针)。然而,更主要的性能影响在于,由于编译器在编译时通常无法确定具体调用哪个函数,导致许多优化(如函数内联)无法进行,并且可能影响处理器的分支预测效率。
纯虚函数/纯虚方法
纯虚函数和抽象类在接口设计中大量使用。如果一个类包含至少一个纯虚函数,则这个类为抽象类。抽象类不能实例化,可以定义指针,因为指针只是一个地址。子类必须实现纯虚函数。因为软件设计中,经常需要定义规范,故需要抽象类。
纯虚函数 = 父类只规定“必须有什么功能”,但不告诉你“具体怎么实现”。 抽象类 = 包含纯虚函数的“接口模板”,不能直接创建对象,只能被继承。
纯虚函数
class Animal { public: virtual void Speak() = 0; // 接口,表示这个函数没有实现,必须由子类实现。 };
纯虚函数通常只有声明没有定义,但是虚析构函数必须有实现,因为派生类析构时一定会调用析构函数。

接口基类必须有virtual析构函数,
class A { public: ~A() = default; // c++11引入,使用编译器自动生成的默认实现。 };
抽象接口类只是接口,本身没有资源,故其西沟函数使用default足够。
代码示例
virtual_1.cc Show() 不是虚函数,因此调用基类版本。

#include <iostream> using namespace std; class Base { public: void Func() { cout<<"Base::Func"<<endl; Show(); } void Show() { cout<<"Base::Show"<<endl; } }; class Derived:public Base { public: void Show() { cout<<"Derived::Show"<<endl; } }; int main() { Derived d; d.Func(); return 0; }
View Code
output:
virtual_2.cc Show() 是虚函数,发生动态绑定,调用派生类版本。

#include <iostream> using namespace std; class Base { public: void Func() { cout<<"Base::Func"<<endl; Show(); } virtual void Show() { cout<<"Base::Show"<<endl; } }; class Derived:public Base { public: void Show() override { cout<<"Derived::Show"<<endl; } }; int main() { Derived d; d.Func(); return 0; }
View Code
output:
virtual_3.cc 代码通过 Base::Show() 明确指定调用基类版本,绕过虚函数动态绑定。
如果明确指定Base::Show();,即强制调用基类版本,则不会动态绑定。

#include <iostream> using namespace std; class Base { public: void Func() { cout<<"Base::Func"<<endl; Base::Show(); // 强制调用基类版本。 } virtual void Show() { cout<<"Base::Show"<<endl; } }; class Derived:public Base { public: void Show() override { cout<<"Derived::Show"<<endl; } }; int main() { Derived d; d.Func(); return 0; }
View Code
output:
virtual_4.cc 基类构造期间调用虚函数时,不会分派到尚未构造完成的派生类,因此调用 Base::Init()。
构造函数/析构函数,构造期间,对象还没有构造完成,故虚函数不会向下调用。

#include <iostream> using namespace std; class Base { public: Base() { Init(); } virtual void Init() { cout<<"Base::Init"; } void Func() { cout<<"Base::Func"<<endl; Show(); } virtual void Show() { cout<<"Base::Show"<<endl; } }; class Derived:public Base { public: void Init() override { cout<<"Derived::Init"; } void Show() override { cout<<"Derived::Show"<<endl; } }; int main() { Derived d; return 0; }
View Code
output:
参考
1. virtual百科;
完
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。