分享
 
 
 

恶草丛生的阴暗角落---虚拟机制(上)

王朝vc·作者佚名  2006-01-08
窄屏简体版  字體: |||超大  

摘要:C++是一个恶草丛生的地带,虚拟机制是很重要,但是很危险的一个C++特性,所以有必要对此作一下阐述,希望能对大家有所帮助。

什么是虚函数

简单地说,就是在成员函数前加关键字virtual,这样这个成员函数就变成了虚函数。

虚函数的思想是从Simula借来得,在C++里面算得上最显著的特征。

虚函数允许派生类取代基类所提供的实现。编译器确保当对象为派生类时,派生类的实现总是被调用,即使对象是使用基类指针访问而不是派生类的指针。

我们为什么要用虚函数

在虚函数开始被加进来的时候,人们对这个东东抱有强烈的抵触意识,有一种常见的说法是:虚函数不过是一个蹩脚的函数指针,完全是多余的。更有甚者,说什么良好的设计根本就不需要虚函数所提供的那些可扩展性和开放性。在这些观点别大肆批判之后,又产生了一种变形:虚函数值不过是一种低效的形式。为此大师们展开了一场保家卫国的战争,道路十分曲折,我不打算把这些观点重复一遍,如果你十分感兴趣,可以参考:The D&V of C++,TC++PL,What is OOP?在这里呢,我大概说一下他的重要性:

从面向对象的角度看,如果没有虚函数,C++ 就不能算是面向对象的了。虽然重载很好,但不要忘了,它只是C概念中传递一个结构的指针给函数的句法装饰而已;虽然标准库包含了许多模板以实现同样非常好的“泛型编程”技术,但虚函数仍然是用C++进行面向对象编程的核心,通过在子类中override基类中的虚函数,就可以达到OO中的一个重要特性——多态。

从商业角度看,如果没有虚函数,C++就不是面向对象的了,自然的我们也就没有什么理由要从C转到C++了。如果没有面向对象,我们就没有足够的理由去培训开发者、开发新工具,如果我们只有C++类的语法而没有面向对象的话,就不会减少维护成本,而实际上会增加培训成本。

从语言的角度看,没有虚函数的C++不是面向对象,用类编程而没有动态绑定则只能算作“基于对象”,而不是“面向对象”。抛弃了虚函数,实际上就是抛弃了OO!结果就变成了早期的Ada语言。

虚函数和非虚函数调用方式有什么不同

非虚成员函数是静态确定的,换句话说,该成员函数在编译时就会被静态地选择。

然而,虚成员函数是动态确定的,换句话说,成员函数在运行时才被动态地选择,该选择基于对象的类型,而不是指向该对象的指针或引用的类型。这被称作“动态绑定”。大多数的编译器使用以下的一些的技术:如果对象有一个或多个虚函数,编译器将一个隐藏的指针放入对象,该指针称为vptr。这个vptr指向一个全局表,该表称为vtbl。在分发一个虚函数时,运行时系统跟随对象的vptr找到类的vtbl,然后跟随vtbl中适当的项找到方法的代码。

虚函数对象的空间开销:每个对象一个额外的指针,加上每个方法一个额外的指针。

虚函数对象的时间开销:和普通函数调用比较,虚函数调用需要两个额外的步骤。

附:这里没有涉及诸如多继承,虚继承等内容,也没有涉及到我们已经说过的RTTI机制,更没有涉及诸如page fault,通过指向函数的指针调用函数等时空论的内容。

虚函数和重载有什么不同

虚函数看来于函数重载有些共通之处,但是函数重载在编译期间就可以确定下来我们要

使用的函数,是可预测的;而虚函数在运行时刻才能确定到具体的函数,是不可预测的

,对于虚函数这一特性有一个专用术语----晚绑定,运用虚函数这种方法叫做函数覆盖。

虚函数遭遇内联

呵呵,一个有趣的问题,但是回答往往不尽人意,特别是初学者更是如此。我发现初学者普遍认为序函数不可能是内联的,原因看起来似乎也很明显:

(1)虚函数是在运行时机制而内联函数特性是一个编译时的机制;

(2)声明一个内联的虚函数会使程序在执行的时间的产生多个函数拷贝,这将导致大量的空间的浪费。

其实,在许多情况下,虚函数是都是静态确定的--特别是当派生类的虚方法调用其基类的方法时。你也许很奇怪为什么会这么做呢?答案很简单,就两个字:封装。一个很好的例子是,派生类的析构函数引起基类的析构函数的调用。除了最初的函数,其他的函数都是静态确定的。如果不确定基类析构函数为内联,就不能发挥这一优点。特别是在继承层次很深,并且许多对象被析构的时候,对虚函数进行内联毫无疑问会大大提高程序的运行效率。

我们再举一个例子例子:

class Shape

{

public:

inline virual void draw()=0;

};

inline void Shape::draw()

{ cout<<"Shape::draw()"<<endl; }

class Rectangle:public Shape

{

public:

void draw()

{ Shape::draw(); cout<<"Rectangle::draw()"<<endl; }

};

Shape* p=new Rectangle;

p->draw();

这个draw是内联的吗?不,当然不是。这要通过虚函数机制在运行时刻确定。这一调用被转换为类似于下面的一些东西:

( *p->vptr[ 1] )( p );

1代表draw在虚函数列表中的位置。因为这个draw的调用通过函数指针_vptr[1]来实现,编译器不能再编译时刻确定调用函数的地址,所以函数不可为内联。

当然,内联虚函数draw的定义必须在某个地方出现以保证执行代码调用的恰当的运行。也就是,至少需要一个定义来在虚函数列表中放置它的地址。编译器如何确定在什么时候生成那个定义呢?一个方法是在虚函数列表生成的时候就生成定义。这意味着为每个类的实例生成一个虚函数列表。每一个内联函数的实例也同时产生。

在一个可执行程序中为一个类要生成多少虚函数列表呢?恩,虽然标准对虚函数的行为做了一些规定;但是没有对实现做出约束。因为虚函数列表没有在标准中做出规定。所以明显也不会去规定如何控制虚函数列表或者生成多少实例。

此外,C++标准现在要求内联函数表现得好象在一个程序中只有一个内联函数的定义,即使函数是在不同的文件中定义的。新的规定是使实现表现为只有一个实例产生。当标准的这个特性被广泛实现的时候涉及到代码膨胀的潜在问题也将会消失。

未完(待续...)

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有