C++ FAQ Lite[20]--继承(虚函数)(更新)

原创 2001年04月25日 17:24:00

[20] 继承 — 虚函数
(Part of C++ FAQ Lite, Copyright © 1991-2001, Marshall Cline, cline@parashift.com)

简体中文版翻译:申旻nicrosoft@sunistudio.com东日制作室东日文档


FAQs in section [20]:


[20.1] 什么是“虚成员函数”?

从面向对象观点来看,它是 C++ 最重要的特征:[6.8], [6.9].

虚函数允许派生类取代基类所提供的实现。编译器确保当对象为派生类时,取代者(译注:即派生类的实现)总是被调用,即使对象是使用基类指针访问而不是派生类的指针。这样就允许基类的算法被派生类取代,即使用户不知道派生类的细节。

派生类可以完全地取代基类成员函数(覆盖(override)),也可以部分地取代基类成员函数(增大(augment))。如果愿意的话,后者由派生类成员函数调用基类成员函数来完成。

TopBottomPrevious sectionNext section ]


[20.2] C++ 怎样同时实现动态绑定和静态类型?

当你有一个对象的指针,而对象实际是该指针类型的派生类(例如:一个 Vehicle*指针实际指向一个Car 对象)。由此有两种类型:指针的(静态)类型(在此是Verhicle),和指向的对象的(动态)类型(在此是Car)。

静态类型意味着成员函数调用的合法性被尽可能早地检查:编译器在编译时。编译器用指针的静态类型决定成员函数调用是否合法。如果指针类型能够处理成员函数,那么指针所指对象当然能很好的处理它。例如,如果 Vehicle 有某个成员函数,则由于Car是一种Vehicle,那么Car 当然也有该成员函数。

动态绑定意味着成员函数调用的代码地址在最终时刻才被决定:基于运行时的对象动态类型。因为绑定到实际被调用的代码这个过程是动态完成的(在运行时),所以被称为“动态绑定”。动态绑定是虚函数导致的结果之一。

TopBottomPrevious sectionNext section ]


[20.3] 虚成员函数和非虚成员函数调用方式有什么不同?

非虚成员函数是静态确定的。也就是说,该成员函数(在编译时)被静态地选择,该选择基于指象对象的指针(或引用)的类型。

相比而言,虚成员函数是动态确定的(在运行时)。也就是说,成员函数(在运行时)被动态地选择,该选择基于对象的类型,而不是指向该对象的指针/引用的类型。这被称作“动态绑定”。大多数的编译器使用以下的一些的技术:如果对象有一个或多个虚函数,编译器将一个
隐藏的指针放入对象,该指针称为“virtual-pointor”或“v-pointer”。这个v-pointer指向一个全局表,该表称为“虚函数表(virtural-table)”或“v-table”。

编译器为每个含有至少一个虚函数的类创建一个v-table。例如,如果Cirle类有虚函数ddraw()move()resize(),那么将有且只有一个和Cricle类相关的v-table,即使有一大堆Circle对象。并且每个 Circle对象的 v-poiner将指向 Circle的这个 v-table。该 v-table自己有指向类的各个虚函数的指针。例如,Circle 的v-table 会有三个指针:一个指向Circle::draw(),一个指向 Circle::move(),还有一个指向Circle::resize()

在分发一个虚函数时,运行时系统跟随对象的 v-pointer找到类的 v-table,然后跟随v-table中适当的项找到方法的代码。

以上技术的空间开销是存在的:每个对象一个额外的指针(仅仅对于需要动态绑定的对象),加上每个方法一个额外的指针(仅仅对于虚方法)。时间开销也是有的:和普通函数调用比较,虚函数调用需要两个额外的步骤(得到v-pointer的值,得到方法的地址)。由于编译器在编译时就通过指针类型解决了非虚函数的调用,所以这些开销不会发生在非虚函数上。

注意:由于没有涉及诸如多继承,虚继承,RTTI等内容,也没有涉及诸如page fault,通过指向函数的指针调用函数等空间/时间论的内容,所以以上讨论是相当简单的。如果你想知道其他的内容,请询问 comp.lang.c++;而不要给我发E-MAIL!

TopBottomPrevious sectionNext section ]


[20.4] 析构函数何时该时虚拟的?

当你可能通过基类指针删除派生类对象时。

虚函数绑定到对象的类的代码,而不是指针/引用的类。如果基类有虚析构函数,delete basePtr时(译注:即基类指针),*basePtr 的对象类型的析构函数被调用,而不是该指针的类型的析构函数。这通常是一件好事情。

TECHNO-GEEK WARNING; PUT YOUR PROPELLER HAT ON.
从技术上来说,如果你打算允许其他人通过基类指针调用对象的析构函数(通过delete这样做是正常的),并且被析构的对象是有重要的析构函数的派生类的对象,就需要让基类的析构函数成为虚拟的。如果一个类有显式的析构函数,或者有成员对象,该成员对象或基类有重要的析构函数,那么这个类就有重要的析构函数。(注意这是一个递归的定义(例如,某个具有重要析构函数的类,它有一个成员对象(它有基类(该基类有成员对象(它有基类(该基类有显式的析构函数))))))
END TECHNO-GEEK WARNING; REMOVE YOUR PROPELLER HAT

如果你对以上的规则理解有困难,试试这个简单的:类应该有虚析构函数,除非这个类没有虚函数。原理:如果有虚函数,说明你想通过基类指针来使用派生对象,并且你所可能做的事情之中,可能包含了调用析构函数(通常通过delete隐含完成)。一旦你在类中加上了一个虚函数,你就已经需要为每一个对象支付空间代价(每个对象一个指针;注意这是理论上的编译器特性;实际上每个编译器都是这样做的),所以这时使析构函数成为虚拟的通常不会额外付出什么。

TopBottomPrevious sectionNext section ]


[20.5] 什么是“虚构造函数(virtual constructor)”?

一种允许你做一些 C++ 不直接支持的事情的用法。

你可能通过虚函数 virtual clone()(对于拷贝构造函数)或虚函数 virtual create()(对于默认构造函数),得到虚构造函数产生的效果。

 class Shape {
 public:
   virtual ~Shape() { }                 
// 虚析构函数
   virtual void draw() = 0;             
// 纯虚函数
   virtual void move() = 0;
   
// ...
   virtual Shape* clone()  const = 0;   
// 使用拷贝构造函数
   virtual Shape* create() const = 0;   
// 使用默认构造函数
 };
 
 class Circle : public Shape {
 public:
   Circle* clone()  const { return new Circle(*this); }
   Circle* create() const { return new Circle();      }
   
// ...
 };

在 clone() 成员函数中,代码 new Circle(*this) 调用 Circle 的拷贝构造函数来复制this的状态到新创建的Circle对象。在 create()成员函数中,代码 new Circle() 调用Circle默认构造函数

用户将它们看作“虚构造函数”来使用它们:

 void userCode(Shape& s)
 {
   Shape* s2 = s.clone();
   Shape* s3 = s.create();
   
// ...
   delete s2;    
// 在此处,你可能需要虚析构函数
   delete s3;
 }

这个函数将正确工作,而不管 Shape 是一个CircleSquare,或是其他种类的 Shape,甚至它们还并不存在。

注意:成员函数Circle's clone()的返回值类型故意与成员函数Shape's clone()的不同。这种特征被称为“协变的返回类型”,该特征最初并不是语言的一部分。如果你的编译器不允许在Circle类中这样声明Circle* clone() const(如,提示“The return type is different”或“The member function's type differs from the base class virtual function by return type alone”),说明你的编译器陈旧了,那么你必须改变返回类型为Shape*。

TopBottomPrevious sectionNext section ]


E-Mail E-mail the author
C++ FAQ LiteTable of contentsSubject indexAbout the author©Download your own copy ]
Revised Apr 8, 2001

C++继承中虚函数的使用

一:继承中的指针问题。 1. 指向基类的指针可以指向派生类对象,当基类指针指向派生类对象时,这种指针只能访问派生对象从基类继承 而来的那些成员,不能访问子类特有的元素 ,除非应用强类型转换,例...
  • pzw0416
  • pzw0416
  • 2012年03月30日 17:19
  • 12622

关于C++虚继承、虚函数继承的几个例子

今天看虚继承以及虚函数继承时写了几个实例来加深一下理解。相关技术原理参见:c++虚继承对象的内存布局 代码1: 没有虚继承的情况下 #include using namespace std; clas...
  • memewry
  • memewry
  • 2012年08月04日 20:58
  • 1866

C++继承、虚函数处的面试题

昨天,收到 SenseTime公司面试官的电话面试(一天面了三家公司,收获挺多的),通话时间将近1个半小时,面试过程中暴露出很多知识上的漏洞,本篇文章针对面试过程中继承以及虚函数方面的知识做一总结,查...
  • ZongYinHu
  • ZongYinHu
  • 2016年04月28日 19:48
  • 4821

C++ FAQ Lite[22]--继承(抽象基类)(更新)

[22] 继承 — 抽象基类(ABCs)(Part of C++ FAQ Lite, Copyright © 1991-2001, Marshall Cline, cline@parashift.co...
  • Nicrosoft
  • Nicrosoft
  • 2001年04月30日 12:19
  • 1034

关于C++虚函数默认参数的问题。Effective C++ 条款38: 决不要重新定义继承而来的缺省参数值

昨晚在chgaowei的博客上关于讨论C++虚函数的默认参数问题,刚翻书找了一下,在Effective C++ 中的38条有说明。直接上原文吧,最后加几句细点的理解条款38: 决不要重新定义继承而来的...
  • hw_henry2008
  • hw_henry2008
  • 2011年05月23日 10:49
  • 5188

C++ FAQ Lite[19]--继承(基础)(更新)

[19] 继承 — 基础(Part of C++ FAQ Lite, Copyright © 1991-2001, Marshall Cline, cline@parashift.com)简体中文版翻...
  • Nicrosoft
  • Nicrosoft
  • 2001年04月23日 22:04
  • 919

C++类有继承时,析构函数必须为虚函数

虚函数与多态一文中讲了虚函数的用法和要点,但少讲了一点,就是虚函数在析构中的用法,本文就是修复一bug的。C++类有继承时,析构函数必须为虚函数。如果不是虚函数,则使用时可能存在内在泄漏的问题。假设我...
  • luoweifu
  • luoweifu
  • 2016年12月21日 09:24
  • 2462

C++虚函数访问权限的改变

如果在基类中虚函数的访问权限是一种情况,那么派生类在继承基类的时候,派生类可以重新定义基类虚函数的访问权限,经过 实例验证是正确的。 从这里也说明了函数的覆盖或者说重定义跟前面的访问权限修饰没多大...
  • zhuhuangtianzi
  • zhuhuangtianzi
  • 2014年12月14日 16:17
  • 940

C++ FAQ学习笔记 20章 继承-虚函数

[20.1] 什么是“虚成员函数”? 派生类可以完全地取代基类成员函数(覆盖(override)),也可以部分地取代基类成员函数(增大(augment))。如果愿意的话,后者由派生类成员函数调用基类成...
  • u010278548
  • u010278548
  • 2013年08月29日 00:20
  • 447

C++ 深入理解 虚继承、多重继承和直接继承

本文从5段代码实例出发,通过类中类的普通继承,类的虚继承,类的多重继承,多个虚函数类的普通继承、虚继承与多重继承,几个交叉概念,详细的阐释了继承、虚函数与虚继承的基本概念,深入剖析了继承于虚继承的区别...
  • u013630349
  • u013630349
  • 2015年07月25日 16:54
  • 7008
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:C++ FAQ Lite[20]--继承(虚函数)(更新)
举报原因:
原因补充:

(最多只允许输入30个字)