虚拟函数

计算机领域术语

虚拟函数是C++语言引入的一个很重要的特性,它提供了“动态绑定”机制,正是这一机制使得继承的语义变得相对明晰。对继承体系的使用者而言,此继承体系内部的多样性是“透明的”。它不必关心其继承细节,处理的就是一组对它而言整体行为一致的“对象”。

虚拟函数的特性
虚拟函数是C++语言引入的一个很重要的特性,它提供了“动态绑定”机制,正是这一机制使得继承的语义变得相对明晰。
(1)基类抽象了通用的数据及操作,就数据而言,如果该数据成员在各派生类中都需要用到,那么就需要将其声明在基类中;就操作而言,如果该操作对各派生类都有意义,无论其语义是否会被修改或扩展,那么就需要将其声明在基类中。
(2)有些操作,如果对于各个派生类而言,语义保持完全一致,而无需修改或扩展,那么这些操作声明为基类的非虚拟成员函数。各派生类在声明为基类的派生类时,默认继承了这些非虚拟成员函数的声明/实现,如同默认继承基类的数据成员一样,而不必另外做任何声明,这就是继承带来的代码重用的优点。
(3)另外还有一些操作,虽然对于各派生类而言都有意义,但是其语义并不相同。这时,这些操作应该声明为基类的虚拟成员函数。各派生类虽然也默认继承了这些虚拟成员函数的声明/实现,但是语义上它们应该对这些虚拟成员函数的实现进行修改或者扩展。另外在实现这些修改或扩展过程中,需要用到额外的该派生类独有的数据时,将这些数据声明为此派生类自己的数据成员。
再考虑更大背景下的继承体系,当更高层次的程序框架(继承体系的使用者)使用此继承体系时,它处理的是一个抽象层次的对象集合(即基类)。虽然这个对象集合的成员实质上可能是各种派生类对象,但在处理这个对象集合中的对象时,它用的是抽象层次的操作。并不区分在这些操作中,哪些操作对各派生类来说是保持不变的,而哪些操作对各派生类来说有所不同。这是因为,当运行时实际执行到各操作时,运行时系统能够识别哪些操作需要用到“动态绑定”,从而找到对应此派生类的修改或扩展的该操作版本。
也就是说,即只需关心它自己问题域的业务逻辑,只要保证正确,其任务就算完成了。即使继承体系内部增加了某种派生类,或者删除了某种派生类,或者某某派生类的某个虚拟函数的实现发生了改变,它的代码不必任何修改。这也意味着,程序的模块化程度得到了极大的提高。而模块化的提高也就意味着可扩展性可维护性,以及代码的可读性的提高,这也是“面向对象”编程的一个很大的优点。
实例展示
下面通过一个简单的实例来展示这一优点。
假设有一个绘图程序允许用户在一个画布上绘制各种图形,如三角形、矩形和圆等,很自然地抽象图形的继承体系,如图1所示。
这个图形继承体系的设计大致如下:
为简单起见,让每个Shape对象都支持“绘制”和“旋转”操作,每个Shape的派生类对这两个操作都有自己的实现:
再来考虑这个图形继承体系的使用,这里很自然的一个使用者是画布,设计其类名为“Canvas”:
Canvas类中维护一个包含所有图形的shapes,Canvas类在处理自己的业务逻辑时并不关心shapes实际上都是哪些具体的图形;相反,如①处和②处所示,它只将这些图形作为一个抽象,即Shape。在处理每个Shape时,调用每个Shape的某个操作即可。
这样做的一个好处是当图形继承体系发生变化时,作为图形继承体系的使用者Canvas而言,它的改变几乎没有,或者很小。
比如说,在程序的演变过程中发现需要支持多边型(Polygon)和贝塞尔曲线(Bezier)类型,只需要在图形继承体系中增加这两个新类型即可:
而不必修改Canvas的任何代码,程序即可像以前那样正常运行。同理,如果以后发现不再支持某种类型,也只需要将其从图形继承体系中删除,而不必修改Canvas的任何代码。可以看到,从对象继承体系的使用者(Canvas)的角度来看,它只看到Shape对象,而不必关心到底是哪一种特定的Shape,这是面向对象设计的一个重要特点和优点。
虚拟函数的“动态绑定”
虚拟函数的“动态绑定”特性虽然很好,但也有其内在的空间以及时间开销,每个支持虚拟函数的类(基类派生类)都会有一个包含其所有支持的虚拟函数指针的“虚拟函数表”(virtual table)。另外每个该类生成的对象都会隐含一个“虚拟函数指针”(virtual pointer),此指针指向其所属类的“虚拟函数表”。当通过基类的指针或者引用调用某个虚拟函数时,系统需要首先定位这个指针或引用真正对应的“对象”所隐含的虚拟函数指针。“虚拟函数指针”,然后根据这个虚拟函数的名称,对这个虚拟函数指针所指向的虚拟函数表进行一个偏移定位,再调用这个偏移定位处的函数指针对应的虚拟函数,这就是“动态绑定”的解析过程(当然C++规范只需要编译器能够保证动态绑定的语义即可,但是绝大多数的C++编译器都是用这种方式实现虚拟函数的),通过分析,不难发现虚拟函数的开销:
—空间:每个支持虚拟函数的类,都有一个虚拟函数表,这个虚拟函数表的大小跟该类拥有的虚拟函数的多少成正比,此虚拟函数表对一个类来说,整个程序只有一个,而无论该类生成的对象在程序运行时会生成多少个。
— 空间:通过支持虚拟函数的类生成的每个对象都有一个指向该类对应的虚拟函数表的虚拟函数指针,无论该类的虚拟函数有多少个,都只有一个函数指针,但是因为与对象绑定,因此程序运行时因为虚拟函数指针引起空间开销跟生成的对象个数成正比。
— 时间:通过支持虚拟函数的类生成的每个对象,当其生成时,在构造函数中会调用编译器在构造函数内部插入的初始化代码,来初始化其虚拟函数指针,使其指向正确的虚拟函数表。
— 时间:当通过指针或者引用调用虚拟函数时,跟普通函数调用相比,会多一个根据虚拟函数指针找到虚拟函数表的操作。
内联函数
因为内联函数常常可以提高代码执行的速度,因此很多普通函数会根据情况进行内联化,但是虚拟函数无法利用内联化的优势,这是因为内联函数是在“编译期”编译器将调用内联函数的地方用内联函数体的代码代替(内联展开),但是虚拟函数本质上是“运行期”行为,本质上在“编译期”编译器无法知道某处的虚拟函数调用在真正执行的时候会调用到那个具体的实现(即在“编译期”无法确定其绑定),因此在“编译期”编译器不会对通过指针或者引用调用的虚拟函数进行内联化。也就是说,如果想利用虚拟函数的“动态绑定”带来的设计优势,那么必须放弃“内联函数”带来的速度优势。
根据上面的分析,似乎在采用虚拟函数时带来和很多的负面影响,但是这些负面影响是否一定是虚拟函数所必须带来的?或者说,如果不采用虚拟函数,是否一定能避免这些缺陷?
还是分析以上图形继承体系的例子,假设不采用虚拟函数,但同时还要实现与上面一样的功能(维持程序的设计语义不变),那么对于基类Shape必须增加一个类型标识成员变量用来在运行时识别到底是哪一个具体的派生类对象:
如①处和②处所示,增加type用来标识派生类对象的具体类型。另外注意这时③处和④处此时已经不再使用virtual声明。
其各派生类在构造时,必须设置具体类型,以Circle派生类为例:
对图形继承体系的使用者(这里是Canvas)而言,其Paint和RotateSelected也需要修改:
程序功能
因为要实现相同的程序功能(语义),已经看到,每个对象虽然没有编译器生成的虚拟函数指针析构函数往往被设计为virtual,如果如此,仍然免不了会隐含增加一个虚拟函数指针,这里假设不是这样),但是还是需要另外增加一个type变量用来标识派生类的类型。构造对象时,虽然不必初始化虚拟函数指针,但是仍然需要初始化type。另外,图形继承体系的使用者调用函数时虽然不再需要一次间接的根据虚拟函数表找寻虚拟函数指针的操作,但是再调用之前,仍然需要一个switch语句对其类型进行识别。
综上所述,这里列举的5条虚拟函数带来的缺陷只剩下两条,即虚拟函数表的空间开销及无法利用“内联函数”的速度优势。再考虑虚拟函数表,每一个含有虚拟函数的类在整个程序中只会有一个虚拟函数表。可以想像到虚拟函数表引起的空间开销实际上是非常小的,几乎可以忽略不计。
性能缺陷
这样可以得出结论,即虚拟函数引入的性能缺陷只是无法利用内联函数
可以进一步设想,非虚拟函数的常规设计假如需要增加一种新的图形类型,或者删除一种不再支持的图形类型,都必须修改该图形系统所有使用者的所有与类型相关的函数调用的代码。这里使用者只有Canvas一个,与类型相关的函数调用代码也只有Paint和RotateSelected两处。但是在一个复杂的程序中,其使用者很多。并且类型相关的函数调用很多时,每次对图形系统的修改都会波及到这些使用者。可以看出不使用虚拟函数的常规设计增加了代码的耦合度模块化不强,因此带来的可扩展性可维护性,以及代码的可读性方面都极大降低。面向对象编程的一个重要目的就是增加程序的可扩展性和可维护性,即当程序的业务逻辑发生变化时,对原有程序的修改非常方便。而不至于对原有代码大动干戈,从而降低因为业务逻辑的改变而增加出错的可能性。根据这点分析,虚拟函数可以大大提升程序的可扩展性及可维护性。
因此在性能和其他方面特性的选择方面,需要开发人员根据实际情况进行权衡和取舍。当然在权衡之前,需要通过性能检测确认性能的瓶颈是由于虚拟函数没有利用到内联函数的优势这一缺陷引起;否则可以不必考虑虚拟函数的影响。
全国各地天气预报查询

上海市

  • 市辖区
  • 云南省

  • 临沧市
  • 云南省

  • 丽江市
  • 云南省

  • 保山市
  • 云南省

  • 大理白族自治州
  • 云南省

  • 德宏傣族景颇族自治州
  • 云南省

  • 怒江傈僳族自治州
  • 云南省

  • 文山壮族苗族自治州
  • 云南省

  • 昆明市
  • 云南省

  • 昭通市
  • 云南省

  • 普洱市
  • 云南省

  • 曲靖市
  • 云南省

  • 楚雄彝族自治州
  • 云南省

  • 玉溪市
  • 云南省

  • 红河哈尼族彝族自治州
  • 云南省

  • 西双版纳傣族自治州
  • 云南省

  • 迪庆藏族自治州
  • 内蒙古自治区

  • 乌兰察布市
  • 内蒙古自治区

  • 乌海市
  • 内蒙古自治区

  • 兴安盟
  • 内蒙古自治区

  • 包头市
  • 内蒙古自治区

  • 呼伦贝尔市
  • 内蒙古自治区

  • 呼和浩特市
  • 内蒙古自治区

  • 巴彦淖尔市
  • 内蒙古自治区

  • 赤峰市
  • 内蒙古自治区

  • 通辽市
  • 内蒙古自治区

  • 鄂尔多斯市
  • 内蒙古自治区

  • 锡林郭勒盟
  • 内蒙古自治区

  • 阿拉善盟
  • 北京市

  • 市辖区
  • 吉林省

  • 吉林市
  • 吉林省

  • 四平市
  • 吉林省

  • 延边朝鲜族自治州
  • 吉林省

  • 松原市
  • 吉林省

  • 白城市
  • 吉林省

  • 白山市
  • 吉林省

  • 辽源市
  • 吉林省

  • 通化市
  • 吉林省

  • 长春市
  • 四川省

  • 乐山市
  • 四川省

  • 内江市
  • 四川省

  • 凉山彝族自治州
  • 四川省

  • 南充市
  • 四川省

  • 宜宾市
  • 四川省

  • 巴中市
  • 四川省

  • 广元市
  • 四川省

  • 广安市
  • 四川省

  • 德阳市
  • 四川省

  • 成都市
  • 四川省

  • 攀枝花市
  • 四川省

  • 泸州市
  • 四川省

  • 甘孜藏族自治州
  • 四川省

  • 眉山市
  • 四川省

  • 绵阳市
  • 四川省

  • 自贡市
  • 四川省

  • 资阳市
  • 四川省

  • 达州市
  • 四川省

  • 遂宁市
  • 四川省

  • 阿坝藏族羌族自治州
  • 四川省

  • 雅安市
  • 天津市

  • 市辖区
  • 宁夏回族自治区

  • 中卫市
  • 宁夏回族自治区

  • 吴忠市
  • 宁夏回族自治区

  • 固原市
  • 宁夏回族自治区

  • 石嘴山市
  • 宁夏回族自治区

  • 银川市
  • 安徽省

  • 亳州市
  • 安徽省

  • 六安市
  • 安徽省

  • 合肥市
  • 安徽省

  • 安庆市
  • 安徽省

  • 宣城市
  • 安徽省

  • 宿州市
  • 安徽省

  • 池州市
  • 安徽省

  • 淮北市
  • 安徽省

  • 淮南市
  • 安徽省

  • 滁州市
  • 安徽省

  • 芜湖市
  • 安徽省

  • 蚌埠市
  • 安徽省

  • 铜陵市
  • 安徽省

  • 阜阳市
  • 安徽省

  • 马鞍山市
  • 安徽省

  • 黄山市
  • 山东省

  • 东营市
  • 山东省

  • 临沂市
  • 山东省

  • 威海市
  • 山东省

  • 德州市
  • 山东省

  • 日照市
  • 山东省

  • 枣庄市
  • 山东省

  • 泰安市
  • 山东省

  • 济南市
  • 山东省

  • 济宁市
  • 山东省

  • 淄博市
  • 山东省

  • 滨州市
  • 山东省

  • 潍坊市
  • 山东省

  • 烟台市
  • 山东省

  • 聊城市
  • 山东省

  • 菏泽市
  • 山东省

  • 青岛市
  • 山西省

  • 临汾市
  • 山西省

  • 吕梁市
  • 山西省

  • 大同市
  • 山西省

  • 太原市
  • 山西省

  • 忻州市
  • 山西省

  • 晋中市
  • 山西省

  • 晋城市
  • 山西省

  • 朔州市
  • 山西省

  • 运城市
  • 山西省

  • 长治市
  • 山西省

  • 阳泉市
  • 广东省

  • 东莞市
  • 广东省

  • 中山市
  • 广东省

  • 云浮市
  • 广东省

  • 佛山市
  • 广东省

  • 广州市
  • 广东省

  • 惠州市
  • 广东省

  • 揭阳市
  • 广东省

  • 梅州市
  • 广东省

  • 汕头市
  • 广东省

  • 汕尾市
  • 广东省

  • 江门市
  • 广东省

  • 河源市
  • 广东省

  • 深圳市
  • 广东省

  • 清远市
  • 广东省

  • 湛江市
  • 广东省

  • 潮州市
  • 广东省

  • 珠海市
  • 广东省

  • 肇庆市
  • 广东省

  • 茂名市
  • 广东省

  • 阳江市
  • 广东省

  • 韶关市
  • 广西壮族自治区

  • 北海市
  • 广西壮族自治区

  • 南宁市
  • 广西壮族自治区

  • 崇左市
  • 广西壮族自治区

  • 来宾市
  • 广西壮族自治区

  • 柳州市
  • 广西壮族自治区

  • 桂林市
  • 广西壮族自治区

  • 梧州市
  • 广西壮族自治区

  • 河池市
  • 广西壮族自治区

  • 玉林市
  • 广西壮族自治区

  • 百色市
  • 广西壮族自治区

  • 贵港市
  • 广西壮族自治区

  • 贺州市
  • 广西壮族自治区

  • 钦州市
  • 广西壮族自治区

  • 防城港市
  • 新疆维吾尔自治区

  • 乌鲁木齐市
  • 新疆维吾尔自治区

  • 伊犁哈萨克自治州
  • 新疆维吾尔自治区

  • 克孜勒苏柯尔克孜自治州
  • 新疆维吾尔自治区

  • 克拉玛依市
  • 新疆维吾尔自治区

  • 博尔塔拉蒙古自治州
  • 新疆维吾尔自治区

  • 吐鲁番市
  • 新疆维吾尔自治区

  • 和田地区
  • 新疆维吾尔自治区

  • 哈密市
  • 新疆维吾尔自治区

  • 喀什地区
  • 新疆维吾尔自治区

  • 塔城地区
  • 新疆维吾尔自治区

  • 巴音郭楞蒙古自治州
  • 新疆维吾尔自治区

  • 昌吉回族自治州
  • 新疆维吾尔自治区

  • 自治区直辖县级行政区划
  • 新疆维吾尔自治区

  • 阿克苏地区
  • 新疆维吾尔自治区

  • 阿勒泰地区
  • 江苏省

  • 南京市
  • 江苏省

  • 南通市
  • 江苏省

  • 宿迁市
  • 江苏省

  • 常州市
  • 江苏省

  • 徐州市
  • 江苏省

  • 扬州市
  • 江苏省

  • 无锡市
  • 江苏省

  • 泰州市
  • 江苏省

  • 淮安市
  • 江苏省

  • 盐城市
  • 江苏省

  • 苏州市
  • 江苏省

  • 连云港市
  • 江苏省

  • 镇江市
  • 江西省

  • 上饶市
  • 江西省

  • 九江市
  • 江西省

  • 南昌市
  • 江西省

  • 吉安市
  • 江西省

  • 宜春市
  • 江西省

  • 抚州市
  • 江西省

  • 新余市
  • 江西省

  • 景德镇市
  • 江西省

  • 萍乡市
  • 江西省

  • 赣州市
  • 江西省

  • 鹰潭市
  • 河北省

  • 保定市
  • 河北省

  • 唐山市
  • 河北省

  • 廊坊市
  • 河北省

  • 张家口市
  • 河北省

  • 承德市
  • 河北省

  • 沧州市
  • 河北省

  • 石家庄市
  • 河北省

  • 秦皇岛市
  • 河北省

  • 衡水市
  • 河北省

  • 邢台市
  • 河北省

  • 邯郸市
  • 河南省

  • 三门峡市
  • 河南省

  • 信阳市
  • 河南省

  • 南阳市
  • 河南省

  • 周口市
  • 河南省

  • 商丘市
  • 河南省

  • 安阳市
  • 河南省

  • 平顶山市
  • 河南省

  • 开封市
  • 河南省

  • 新乡市
  • 河南省

  • 洛阳市
  • 河南省

  • 漯河市
  • 河南省

  • 濮阳市
  • 河南省

  • 焦作市
  • 河南省

  • 省直辖县级行政区划
  • 河南省

  • 许昌市
  • 河南省

  • 郑州市
  • 河南省

  • 驻马店市
  • 河南省

  • 鹤壁市
  • 浙江省

  • 丽水市
  • 浙江省

  • 台州市
  • 浙江省

  • 嘉兴市
  • 浙江省

  • 宁波市
  • 浙江省

  • 杭州市
  • 浙江省

  • 温州市
  • 浙江省

  • 湖州市
  • 浙江省

  • 绍兴市
  • 浙江省

  • 舟山市
  • 浙江省

  • 衢州市
  • 浙江省

  • 金华市
  • 海南省

  • 三亚市
  • 海南省

  • 三沙市
  • 海南省

  • 儋州市
  • 海南省

  • 海口市
  • 海南省

  • 省直辖县级行政区划
  • 湖北省

  • 十堰市
  • 湖北省

  • 咸宁市
  • 湖北省

  • 孝感市
  • 湖北省

  • 宜昌市
  • 湖北省

  • 恩施土家族苗族自治州
  • 湖北省

  • 武汉市
  • 湖北省

  • 省直辖县级行政区划
  • 湖北省

  • 荆州市
  • 湖北省

  • 荆门市
  • 湖北省

  • 襄阳市
  • 湖北省

  • 鄂州市
  • 湖北省

  • 随州市
  • 湖北省

  • 黄冈市
  • 湖北省

  • 黄石市
  • 湖南省

  • 娄底市
  • 湖南省

  • 岳阳市
  • 湖南省

  • 常德市
  • 湖南省

  • 张家界市
  • 湖南省

  • 怀化市
  • 湖南省

  • 株洲市
  • 湖南省

  • 永州市
  • 湖南省

  • 湘潭市
  • 湖南省

  • 湘西土家族苗族自治州
  • 湖南省

  • 益阳市
  • 湖南省

  • 衡阳市
  • 湖南省

  • 邵阳市
  • 湖南省

  • 郴州市
  • 湖南省

  • 长沙市
  • 甘肃省

  • 临夏回族自治州
  • 甘肃省

  • 兰州市
  • 甘肃省

  • 嘉峪关市
  • 甘肃省

  • 天水市
  • 甘肃省

  • 定西市
  • 甘肃省

  • 平凉市
  • 甘肃省

  • 庆阳市
  • 甘肃省

  • 张掖市
  • 甘肃省

  • 武威市
  • 甘肃省

  • 甘南藏族自治州
  • 甘肃省

  • 白银市
  • 甘肃省

  • 酒泉市
  • 甘肃省

  • 金昌市
  • 甘肃省

  • 陇南市
  • 福建省

  • 三明市
  • 福建省

  • 南平市
  • 福建省

  • 厦门市
  • 福建省

  • 宁德市
  • 福建省

  • 泉州市
  • 福建省

  • 漳州市
  • 福建省

  • 福州市
  • 福建省

  • 莆田市
  • 福建省

  • 龙岩市
  • 西藏自治区

  • 山南市
  • 西藏自治区

  • 拉萨市
  • 西藏自治区

  • 日喀则市
  • 西藏自治区

  • 昌都市
  • 西藏自治区

  • 林芝市
  • 西藏自治区

  • 那曲市
  • 西藏自治区

  • 阿里地区
  • 贵州省

  • 六盘水市
  • 贵州省

  • 安顺市
  • 贵州省

  • 毕节市
  • 贵州省

  • 贵阳市
  • 贵州省

  • 遵义市
  • 贵州省

  • 铜仁市
  • 贵州省

  • 黔东南苗族侗族自治州
  • 贵州省

  • 黔南布依族苗族自治州
  • 贵州省

  • 黔西南布依族苗族自治州
  • 辽宁省

  • 丹东市
  • 辽宁省

  • 大连市
  • 辽宁省

  • 抚顺市
  • 辽宁省

  • 朝阳市
  • 辽宁省

  • 本溪市
  • 辽宁省

  • 沈阳市
  • 辽宁省

  • 盘锦市
  • 辽宁省

  • 营口市
  • 辽宁省

  • 葫芦岛市
  • 辽宁省

  • 辽阳市
  • 辽宁省

  • 铁岭市
  • 辽宁省

  • 锦州市
  • 辽宁省

  • 阜新市
  • 辽宁省

  • 鞍山市
  • 重庆市

  • 重庆市

  • 市辖区
  • 陕西省

  • 咸阳市
  • 陕西省

  • 商洛市
  • 陕西省

  • 安康市
  • 陕西省

  • 宝鸡市
  • 陕西省

  • 延安市
  • 陕西省

  • 榆林市
  • 陕西省

  • 汉中市
  • 陕西省

  • 渭南市
  • 陕西省

  • 西安市
  • 陕西省

  • 铜川市
  • 青海省

  • 果洛藏族自治州
  • 青海省

  • 海东市
  • 青海省

  • 海北藏族自治州
  • 青海省

  • 海南藏族自治州
  • 青海省

  • 海西蒙古族藏族自治州
  • 青海省

  • 玉树藏族自治州
  • 青海省

  • 西宁市
  • 青海省

  • 黄南藏族自治州
  • 黑龙江省

  • 七台河市
  • 黑龙江省

  • 伊春市
  • 黑龙江省

  • 佳木斯市
  • 黑龙江省

  • 双鸭山市
  • 黑龙江省

  • 哈尔滨市
  • 黑龙江省

  • 大兴安岭地区
  • 黑龙江省

  • 大庆市
  • 黑龙江省

  • 牡丹江市
  • 黑龙江省

  • 绥化市
  • 黑龙江省

  • 鸡西市
  • 黑龙江省

  • 鹤岗市
  • 黑龙江省

  • 黑河市
  • 黑龙江省

  • 齐齐哈尔市