虚函数的作用
c++ virtual函数 继承实现例子

C++的虚函数和继承是面向对象编程中非常重要的概念,虚函数通过继承可以实现多态性,为程序设计提供了很大的灵活性。
本文将通过介绍C++中虚函数和继承的特点,结合具体的实现例子来说明其重要性和实际应用。
1. 虚函数的概念与作用虚函数是C++中面向对象编程的重要概念,它允许在派生类中重写基类的函数,实现多态性。
在基类中使用virtual关键字声明函数为虚函数,在派生类中使用override关键字来标识该函数是对基类虚函数的重写。
虚函数的作用在于:- 实现运行时多态性:通过基类指针指向派生类对象,可以根据实际对象的类型来调用相应的函数,实现了动态绑定。
- 可以实现接口多态性:通过虚函数可以定义统一的接口,在不同的派生类中按照自己的需求来实现具体的功能。
2. 继承的概念与特点继承是C++面向对象编程中实现代码重用和扩展的重要机制,通过继承可以创建新类并使用现有类的所有属性和方法。
在C++中,类的继承有公有继承、保护继承和私有继承三种方式,其中公有继承最为常用。
在派生类中可以访问基类的公有成员,但无法访问基类的私有成员。
继承的特点包括:- 实现代码重用:通过继承可以直接使用基类中已有的属性和方法,减少了重复编写代码的工作量。
- 实现代码扩展:通过在派生类中添加新的属性和方法,可以扩展基类的功能,实现更加灵活的代码设计。
3. 虚函数继承实现例子接下来将通过一个具体的实现例子来说明虚函数和继承的重要性和实际应用。
假设有一个基类Animal,其中定义了一个虚函数speak()用于输出动物的叫声,然后有两个派生类Dog和Cat,分别重写了speak()函数以实现不同的叫声输出。
示例代码如下:```cpp#include <iostream>class Animal {public:virtual void speak() {std::cout << "Animal speaking..." << std::endl;}};class Dog : public Animal {public:void speak() override {std::cout << "Dog barking..." << std::endl; }};class Cat : public Animal {public:void speak() override {std::cout << "Cat meowing..." << std::endl; }};int m本人n() {Animal* animal1 = new Dog();Animal* animal2 = new Cat();animal1->speak(); // 输出Dog barking...animal2->speak(); // 输出Cat meowing...delete animal1;delete animal2;return 0;}```在上面的示例中,通过虚函数和继承的结合使用,实现了运行时多态性。
c++虚函数作用及底层原理

c++虚函数作用及底层原理C++是一种面向对象的编程语言,并支持多态性的实现。
其中,虚函数是C++中实现多态性的主要机制之一。
虚函数是一种特殊的成员函数,可以在派生类中重写,并通过基类指针或引用的间接方式调用派生类的实现。
虚函数的作用:1. 实现动态绑定:实现多态性的关键是在运行时确定函数的具体实现。
虚函数通过将函数调用的确定延迟到运行时,而不是在编译时确定,从而实现动态绑定。
2. 多态性:允许派生类重写基类的函数,并使用基类指针或引用调用派生类的实现。
这种多态性的实现可以增强代码的灵活性和可重用性。
3. 实现抽象类:虚函数也可以用于实现抽象类,即只声明函数接口而没有实现。
这样,派生类必须实现虚函数才能被实例化。
虚函数的底层原理:虚函数的实现需要两个关键组件:虚函数表(vtable)和虚函数指针(vptr)。
1. 虚函数表:每个包含虚函数的类都有一个虚函数表,其中包含了类中所有虚函数的地址。
虚函数表是一个静态数据结构,只有一个虚函数表,且在编译时生成。
2. 虚函数指针:每个包含虚函数的类的对象都有一个虚函数指针,指向其所属类的虚函数表。
虚函数指针是一个指向虚函数表的指针,指针的值在程序运行时动态确定。
当调用虚函数时,程序首先通过对象的虚函数指针找到其所属类的虚函数表,然后查找相应的虚函数地址,最终调用正确的虚函数实现。
这样,虚函数的实现在运行时动态确定,实现了动态绑定和多态性。
总之,虚函数是C++中实现多态性的主要机制之一,通过虚函数表和虚函数指针的使用,实现了动态绑定和多态性的实现。
虚函数的应用可以增强代码的灵活性和可重用性。
虚函数

虚函数定义虚函数必须是基类的非静态成员函数,其访问权限可以是protected或public,在基类的类定义中定义虚函数的一般形式:virtual 函数返回值类型虚函数名(形参表){ 函数体}作用虚函数的作用是实现动态联编,也就是在程序的运行阶段动态地选择合适的成员函数,在定义了虚函数后,可以在基类的派生类中对虚函数重新定义,在派生类中重新定义的函数应与虚函数具有相同的形参个数和形参类型。
以实现统一的接口,不同定义过程。
如果在派生类中没有对虚函数重新定义,则它继承其基类的虚函数。
当程序发现虚函数名前的关键字virtual后,会自动将其作为动态联编处理,即在程序运行时动态地选择合适的成员函数。
动态联编规定,只能通过指向基类的指针或基类对象的引用来调用虚函数,其格式:指向基类的指针变量名->虚函数名(实参表)或基类对象的引用名. 虚函数名(实参表)虚函数是C++多态的一种表现例如:子类继承了父类的一个函数(方法),而我们把父类的指针指向子类,则必须把父类的该函数(方法)设为virturl(虚函数)。
使用虚函数,我们可以灵活的进行动态绑定,当然是以一定的开销为代价。
如果父类的函数(方法)根本没有必要或者无法实现,完全要依赖子类去实现的话,可以把此函数(方法)设为virturl 函数名=0 我们把这样的函数(方法)称为纯虚函数。
如果一个类包含了纯虚函数,称此类为抽象类。
示例虚函数的实例:#include<iostream.h>class Cshape{public:void SetColor( int color) { m_nColor=color;}void virtual Display( void) { cout<<"Cshape"<<endl; }private:int m_nColor;};class Crectangle: public Cshape{public:void virtual Display( void) { cout<<"Crectangle"<<endl; } };class Ctriangle: public Cshape{void virtual Display( void) { cout<<"Ctriangle"<<endl; }};class Cellipse :public Cshape{public: void virtual Display(void) { cout<<"Cellipse"<<endl;} };void main(){Cshape obShape;Cellipse obEllipse;Ctriangle obTriangle;Crectangle obRectangle;Cshape * pShape[4]=for( int I= 0; I< 4; I++)pShape[I]->Display( );}本程序运行结果:CshapeCellipseCtriangleCrectangle所以,从以上程序分析,实现动态联编需要三个条件:1、必须把动态联编的行为定义为类的虚函数。
C++中虚函数的作用和虚函数的工作原理

C++中虚函数的作⽤和虚函数的⼯作原理1 C++中虚函数的作⽤和多态虚函数:实现类的多态性关键字:虚函数;虚函数的作⽤;多态性;多态公有继承;动态联编C++中的虚函数的作⽤主要是实现了多态的机制。
基类定义虚函数,⼦类可以重写该函数;在派⽣类中对基类定义的虚函数进⾏重写时,需要在派⽣类中声明该⽅法为虚⽅法。
当⼦类重新定义了⽗类的虚函数后,当⽗类的指针指向⼦类对象的地址时,[即B b; A a = &b;] ⽗类指针根据赋给它的不同⼦类指针,动态的调⽤⼦类的该函数,⽽不是⽗类的函数(如果不使⽤virtual⽅法,请看后⾯★*),且这样的函数调⽤发⽣在运⾏阶段,⽽不是发⽣在编译阶段,称为动态联编。
⽽函数的重载可以认为是多态,只不过是静态的。
注意,⾮虚函数静态联编,效率要⽐虚函数⾼,但是不具备动态联编能⼒。
★如果使⽤了virtual关键字,程序将根据引⽤或指针指向的对象类型来选择⽅法,否则使⽤引⽤类型或指针类型来选择⽅法。
下⾯的例⼦解释动态联编性:class A{private:int i;public:A();A(int num) :i(num) {};virtual void fun1();virtual void fun2();};class B : public A{private:int j;public:B(int num) :j(num){};virtual void fun2();// 重写了基类的⽅法};// 为⽅便解释思想,省略很多代码A a(1);B b(2);A *a1_ptr = &a;A *a2_ptr = &b;// 当派⽣类“重写”了基类的虚⽅法,调⽤该⽅法时// 程序根据指针或引⽤指向的 “对象的类型”来选择使⽤哪个⽅法a1_ptr->fun2();// call A::fun2();a2_ptr->fun2();// call B::fun1();// 否则// 程序根据“指针或引⽤的类型”来选择使⽤哪个⽅法a1_ptr->fun1();// call A::fun1();a2_ptr->fun1();// call A::fun1();2. 虚函数的底层实现机制实现原理:虚函数表+虚表指针关键字:虚函数底层实现机制;虚函数表;虚表指针编译器处理虚函数的⽅法是:为每个类对象添加⼀个隐藏成员,隐藏成员中保存了⼀个指向函数地址数组的指针,称为虚表指针(vptr),这种数组成为虚函数表(virtual function table, vtbl),即,每个类使⽤⼀个虚函数表,每个类对象⽤⼀个虚表指针。
C++虚函数

C++虚函数学习笔记(仅供参考)1、虚函数的作用:允许在派生类中重新定义与基类同名的函数,并且可以通过基类的指针或引用来访问基类和派生类中的同名函数。
2、虚函数的工作原理编译器处理虚函数的方法:对每个对象添加一个隐藏成员。
隐藏成员中保存了一个指向函数地址数组的指针,这种数组称为虚函数表(vtbl)。
虚函数表中存储了为类对象进行声明的虚函数的地址。
例如:基类对象包含一个指针,该指针指向基类中所有虚函数的地址表。
派生类对象将包含一个指向独立地址表的指针如果派生类提供了虚函数的新定义,该虚函数表将保存新函数的地址,如果没有重新定义虚函数该vtbl将保存函数原始版本的地址,如果派生类定义了新的虚函数,则该函数的地址也将被添加到vtbl中。
注:无论类中包含了多少虚函数,都只需在对象中添加一个地址成员,只是表的大小不同。
C++规定:当一个成员函数声明为基类后,其派生类中的同名函数也自动成为虚函数。
虚函数使用的注意事项:一、只能用virtual声明类的成员函数,使它成为虚函数,而不能将类外的普通函数声明为虚函数,因为虚函数的作用是允许在派生类中对基类的虚函数重新定义。
显然,它只能用于类的继承层次结构中。
二、一个成员函数被声明为虚函数后,在同一类族中的类就不能再定义一个非virtual的但与该虚函数具有相同参数的(包括个数和类型)和函数返回值类型的同名函数。
静态关联与动态关联:关联:确定调用具体对象的过程称为关联。
静态关联:函数重载和通过对象名调用的虚函数,在编译时可确定其调用的虚函数属于哪一个类,其过程称为静态关联;动态关联:在运行阶段将类和对象绑定在一起的过程称为动态关联;如:在虚函数中的pt->display(),无法确定确定应调用哪一类的虚函数,在这样的情况下,编译系统把它放到运行阶段处理,在运行阶段确定其关联关系。
在运行阶段,基类指针变量先指向某一个类的对象,然后通过此指针变量调用该对象中的函数,此时调用哪一个对象的函数就确定了。
c++虚函数作用及底层原理

c++虚函数作用及底层原理
c++虚函数作用及底层原理
什么是虚函数?
虚函数是指函数表现出一种多态特性的函数,也就是说在程序中可以根据所操作对象的不同而调用不同的函数。
在函数实现方面,他们就是有一个虚表(Vtable)指向的函数的指针,并且只有用virtual 关键字来声明的函数才会存储在Vtable 中。
虚函数的主要作用:
1、实现多态:当定义的类含有需要多态的函数,那么就要在定义的函数前面加上virtual 关键字,使函数具有虚函数的能力,这样可以实现多态性。
2、实现动态绑定:使用虚函数,可以实现动态绑定,也就是说,当程序在运行时,可以根据所操作对象的不同而调用不同的函数。
虚函数的底层原理:
在c++中的实现原理是采用一个指针数组,也称为虚表(Vtable)实现的。
虚表是一个数组,里面存放的是函数的地址。
当调用虚函数时,程序会首先检查对象是否有虚表指针,如果有系统会拿到虚表指针,然后根据指针所指的虚表的索引号去虚表中找到实际的函数再去调用。
因此,虚函数的实现原理需要以下几步:
1t根据继承关系初始化虚表:程序会在构造函数中初始化虚
表,虚表中存放的是所有虚函数的地址;
2t程序在运行过程中,会检查对象是否有虚表指针;
3t根据指针所指的虚表的索引号去虚表中找到实际的函数再去调用;
4t程序运行结束,会销毁所有的对象。
程序设计二(面向对象)_实训13_虚函数

程序设计二(面向对象)_实训13_虚函数虚函数是面向对象程序设计中的一个重要概念,它在继承和多态的实现中起着关键的作用。
本文将介绍虚函数的概念、作用、使用方法以及一些注意事项。
一、什么是虚函数在面向对象的程序设计中,虚函数是一种特殊的成员函数,它可以在基类中声明为虚函数,并在派生类中进行重写。
通过使用虚函数,我们可以实现多态性,即同一个函数在不同的对象上表现出不同的行为。
二、虚函数的作用虚函数的主要作用是实现动态绑定。
在程序运行时,根据对象的实际类型来确定调用哪个函数。
这使得我们可以通过基类指针或引用来调用派生类的函数,实现了对派生类对象的统一操作。
三、虚函数的使用方法1. 在基类中声明虚函数:在基类中将需要在派生类中重写的函数声明为虚函数。
例如,我们有一个基类Animal,其中有一个虚函数makeSound(),表示动物发出声音。
2. 在派生类中重写虚函数:派生类继承基类后,可以重写基类中的虚函数,并根据实际需求修改函数的实现。
例如,我们有一个派生类Dog,它重写了基类中的makeSound()函数,表示狗的叫声。
3. 使用基类指针或引用调用虚函数:在程序中,我们可以使用基类的指针或引用来调用派生类中重写的虚函数,实现对派生类对象的统一操作。
例如,我们可以使用Animal*指针指向Dog对象,并通过该指针调用makeSound()函数,即使实际对象是Dog类型,也能正确调用Dog类中的makeSound()函数。
四、虚函数的注意事项1. 虚函数必须是非静态成员函数。
虚函数与对象的状态有关,因此必须是非静态成员函数。
2. 虚函数可以被派生类重写,但不能被派生类隐藏。
派生类重写虚函数时,函数的签名必须与基类中的虚函数相同,即函数名、参数列表和返回类型都要相同。
否则,派生类中的函数将隐藏基类中的虚函数,而不是重写。
3. 构造函数和析构函数不能是虚函数。
构造函数和析构函数在对象的创建和销毁过程中起着重要作用,因此不能声明为虚函数。
虚函数的应用和原理

虚函数的应用和原理1. 概述虚函数是面向对象编程的一个重要概念,它允许子类重写父类中定义的函数,从而实现多态性。
本文将介绍虚函数的应用场景和工作原理,并通过代码示例说明具体实现方法。
2. 虚函数的定义和声明在C++中,通过在函数声明前加上关键字virtual来声明虚函数。
例如:virtual void doSomething();虚函数可以在父类中进行定义,子类可以根据需要重写该函数。
3. 虚函数实现多态性虚函数的主要作用是实现多态性(Polymorphism)。
多态性是面向对象编程中一个非常重要的概念,它允许使用同样的代码处理不同类型的对象。
使用虚函数可以通过父类指针或引用来调用子类重写的方法,实现对子类对象的动态调用。
4. 虚函数的应用场景虚函数广泛应用于面向对象编程中的继承体系中,特别是当父类指针指向子类对象时,通过虚函数可以实现对子类方法的动态调用。
以下是一些常见的应用场景:•实现接口和抽象类:通过定义虚函数,可以定义接口和抽象类,子类可以根据需要重写这些虚函数。
•实现运行时多态:通过将父类指针指向子类对象,在运行时动态调用子类的方法,实现多态性。
•实现虚析构函数:虚析构函数可以确保在删除子类对象时,首先调用子类的析构函数,然后再调用父类的析构函数。
5. 虚函数的工作原理在C++中,虚函数的工作原理是通过虚函数表(virtual function table)来实现的。
每个有虚函数的类都包含一个虚函数表,其中存储了该类中所有虚函数的地址。
子类会继承父类的虚函数表,并可以在表中重写父类的虚函数地址。
当通过父类指针或引用调用虚函数时,编译器会根据实际指向的子类对象的类型,在虚函数表中查找对应的函数地址并调用。
6. 虚函数的实现例子以下是一个简单的虚函数的实现例子: ```cpp #includeclass Animal { public: virtual void sound() { std::cout <<。
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
条款14: 确定基类有虚析构函数有时,一个类想跟踪它有多少个对象存在。
一个简单的方法是创建一个静态类成员来统计对象的个数。
这个成员被初始化为0,在构造函数里加1,析构函数里减1。
(条款m26里说明了如何把这种方法封装起来以便很容易地添加到任何类中,“my article on counting objects”提供了对这个技术的另外一些改进)设想在一个军事应用程序里,有一个表示敌人目标的类:class enemytarget {public:enemytarget() { ++numtargets; }enemytarget(const enemytarget&) { ++numtargets; }~enemytarget() { --numtargets; }static size_t numberoftargets(){ return numtargets; }virtual bool destroy(); // 摧毁enemytarget对象后// 返回成功private:static size_t numtargets; // 对象计数器};// 类的静态成员要在类外定义;// 缺省初始化为0size_t enemytarget::numtargets;这个类不会为你赢得一份政府防御合同,它离国防部的要求相差太远了,但它足以满足我们这儿说明问题的需要。
敌人的坦克是一种特殊的敌人目标,所以会很自然地想到将它抽象为一个以公有继承方式从enemytarget派生出来的类(参见条款35及m33)。
因为不但要关心敌人目标的总数,也要关心敌人坦克的总数,所以和基类一样,在派生类里也采用了上面提到的同样的技巧:class enemytank: public enemytarget {public:enemytank() { ++numtanks; }enemytank(const enemytank& rhs): enemytarget(rhs){ ++numtanks; }~enemytank() { --numtanks; }static size_t numberoftanks(){ return numtanks; }virtual bool destroy();private:static size_t numtanks; // 坦克对象计数器};(写完以上两个类的代码后,你就更能够理解条款m26对这个问题的通用解决方案了。
)最后,假设程序的其他某处用new动态创建了一个enemytank对象,然后用delete 删除掉:enemytarget *targetptr = new enemytank;...delete targetptr;到此为止所做的一切好象都很正常:两个类在析构函数里都对构造函数所做的操作进行了清除;应用程序也显然没有错误,用new生成的对象在最后也用delete 删除了。
然而这里却有很大的问题。
程序的行为是不可预测的——无法知道将会发生什么。
c++语言标准关于这个问题的阐述非常清楚:当通过基类的指针去删除派生类的对象,而基类又没有虚析构函数时,结果将是不可确定的。
这意味着编译器生成的代码将会做任何它喜欢的事:重新格式化你的硬盘,给你的老板发电子邮件,把你的程序源代码传真给你的对手,无论什么事都可能发生。
(实际运行时经常发生的是,派生类的析构函数永远不会被调用。
在本例中,这意味着当targetptr 删除时,enemytank的数量值不会改变,那么,敌人坦克的数量就是错的,这对需要高度依赖精确信息的部队来说,会造成什么后果?)为了避免这个问题,只需要使enemytarget的析构函数为virtual。
声明析构函数为虚就会带来你所希望的运行良好的行为:对象内存释放时,enemytank和enemytarget的析构函数都会被调用。
和绝大部分基类一样,现在enemytarget类包含一个虚函数。
虚函数的目的是让派生类去定制自己的行为(见条款36),所以几乎所有的基类都包含虚函数。
如果某个类不包含虚函数,那一般是表示它将不作为一个基类来使用。
当一个类不准备作为基类使用时,使析构函数为虚一般是个坏主意。
请看下面的例子,这个例子基于arm(“the annotated c++ reference manual”)一书的一个专题讨论。
// 一个表示2d点的类class point {public:point(short int xcoord, short int ycoord);~point();private:short int x, y;};如果一个short int占16位,一个point对象将刚好适合放进一个32位的寄存器中。
另外,一个point对象可以作为一个32位的数据传给用c或fortran等其他语言写的函数中。
但如果point的析构函数为虚,情况就会改变。
实现虚函数需要对象附带一些额外信息,以使对象在运行时可以确定该调用哪个虚函数。
对大多数编译器来说,这个额外信息的具体形式是一个称为vptr(虚函数表指针)的指针。
vptr指向的是一个称为vtbl(虚函数表)的函数指针数组。
每个有虚函数的类都附带有一个vtbl。
当对一个对象的某个虚函数进行请求调用时,实际被调用的函数是根据指向vtbl的vptr在vtbl里找到相应的函数指针来确定的。
虚函数实现的细节不重要(当然,如果你感兴趣,可以阅读条款m24),重要的是,如果point类包含一个虚函数,它的对象的体积将不知不觉地翻番,从2个16位的short变成了2个16位的short加上一个32位的vptr!point对象再也不能放到一个32位寄存器中去了。
而且,c++中的point对象看起来再也不具有和其他语言如c中声明的那样相同的结构了,因为这些语言里没有vptr。
所以,用其他语言写的函数来传递point也不再可能了,除非专门去为它们设计vptr,而这本身是实现的细节,会导致代码无法移植。
所以基本的一条是,无故的声明虚析构函数和永远不去声明一样是错误的。
实际上,很多人这样总结:当且仅当类里包含至少一个虚函数的时候才去声明虚析构函数。
这是一个很好的准则,大多数情况都适用。
但不幸的是,当类里没有虚函数的时候,也会带来非虚析构函数问题。
例如,条款13里有个实现用户自定义数组下标上下限的类模板。
假设你(不顾条款m33的建议)决定写一个派生类模板来表示某种可以命名的数组(即每个数组有一个名字)。
template<class t> // 基类模板class array { // (来自条款13)public:array(int lowbound, int highbound);~array();private:vector<t> data;size_t size;int lbound, hbound;};template<class t>class namedarray: public array<t> {public:namedarray(int lowbound, int highbound, const string& name);...private:string arrayname;};如果在应用程序的某个地方你将指向namedarray类型的指针转换成了array类型的指针,然后用delete来删除array指针,那你就会立即掉进“不确定行为”的陷阱中。
namedarray<int> *pna =new namedarray<int>(10, 20, "impending doom");array<int> *pa;...pa = pna; // namedarray<int>* -> array<int>*...delete pa; // 不确定! 实际中,pa->arrayname// 会造成泄漏,因为*pa的namedarray// 永远不会被删除现实中,这种情形出现得比你想象的要频繁。
让一个现有的类做些什么事,然后从它派生一个类做和它相同的事,再加上一些特殊的功能,这在现实中不是不常见。
namedarray没有重定义array的任何行为——它继承了array的所有功能而没有进行任何修改——它只是增加了一些额外的功能。
但非虚析构函数的问题依然存在(还有其他问题,参见m33)最后,值得指出的是,在某些类里声明纯虚析构函数很方便。
纯虚函数将产生抽象类——不能实例化的类(即不能创建此类型的对象)。
有些时候,你想使一个类成为抽象类,但刚好又没有任何纯虚函数。
怎么办?因为抽象类是准备被用做基类的,基类必须要有一个虚析构函数,纯虚函数会产生抽象类,所以方法很简单:在想要成为抽象类的类里声明一个纯虚析构函数。
这里是一个例子:class awov { // awov = "abstract w/o// virtuals"public:virtual ~awov() = 0; // 声明一个纯虚析构函数};这个类有一个纯虚函数,所以它是抽象的,而且它有一个虚析构函数,所以不会产生析构函数问题。
但这里还有一件事:必须提供纯虚析构函数的定义:awov::~awov() {} // 纯虚析构函数的定义这个定义是必需的,因为虚析构函数工作的方式是:最底层的派生类的析构函数最先被调用,然后各个基类的析构函数被调用。
这就是说,即使是抽象类,编译器也要产生对~awov的调用,所以要保证为它提供函数体。
如果不这么做,链接器就会检测出来,最后还是得回去把它添上。
可以在函数里做任何事,但正如上面的例子一样,什么事都不做也不是不常见。
如果是这种情况,那很自然地会想到将析构函数声明为内联函数,从而避免对一个空函数的调用所产生的开销。
这是一个很好的方法,但有一件事要清楚。
因为析构函数为虚,它的地址必须进入到类的vtbl(见条款m24)。
但内联函数不是作为独立的函数存在的(这就是“内联”的意思),所以必须用特殊的方法得到它们的地址。
条款33对此做了全面的介绍,其基本点是:如果声明虚析构函数为inline,将会避免调用它们时产生的开销,但编译器还是必然会在什么地方产生一个此函数的拷贝。