荔园在线

荔园之美,在春之萌芽,在夏之绽放,在秋之收获,在冬之沉淀

[回到开始] [上一篇][下一篇]


发信人: Second (石开), 信区: Program
标  题: Part of C++ FAQ Lite[20]--继承(虚函数)
发信站: 荔园晨风BBS站 (Thu Jun  7 05:51:58 2001), 转信

译文出处:东日文档 http://www.sunistudio.com/
[20] 继承 — 虚函数
(part of c++ faq lite, copyright ? 1991-96, marshall cline, cline@parashift.
com)
----------------------------------------------------------------------------
----
faqs in section [20]:
[20.1] 什么是“虚成员函数”?
[20.2] c++ 怎样同时实现动态绑定和静态类型?
[20.3] 虚成员函数和非虚成员函数调用方式有什么不同?
[20.4] 析构函数何时该时虚拟的?
[20.5] what is a "virtual constructor"?
----------------------------------------------------------------------------
----
[20.1] 什么是“虚成员函数”?
从面向对象观点来看,它是 c++ 最重要的特征[6.8][6.9]。
虚函数允许派生类取代基类的实现。编译器确保当对象为派生类时,取代者(译注:即

生类的实现)总是被调用,即使对象是使用基类指针访问而不是派生类的指针。这样就

许基类的算法被派生类取代,即使用户不知道派生类的细节。
派生类可以完全地取代基类成员函数(覆盖(override)),也可以部分地取代基类成员

数(增大(augment))。如果希望的话,后者由派生类成员函数调用基类成员函数来完成

----------------------------------------------------------------------------
----
[20.2] c++ 怎样同时实现动态绑定和静态类型?
当你有一个对象的指针,而对象实际是该指针类型的派生类(例如:一个 vehicle* 指

实际指向一个 car 对象)。由此有两种类型:指针的(静态)类型(在此是 verhicle
),
和指向的对象的(动态)类型(在此是 car)。
静态类型意味着成员函数调用的合法性被尽可能早地检查:编译器在编译时。编译器用

针的静态类型决定成员函数调用是否合法。如果指针类型能够处理成员函数,那么指针

指对象当然能很好的处理它。例如,如果 vehicle 有某个成员函数,则由于car是一种

vehicle,那么 car 当然也有该成员函数。
动态绑定意味着成员函数调用的代码地址在最终时刻才被决定:基于运行时的对象动态
类型。
因为绑定到实际被调用的代码这个过程是动态完成的(在运行时),所以被称为“动态
绑定”。
动态绑定是虚函数导致的结果之一。
----------------------------------------------------------------------------
----
[20.3] 虚成员函数和非虚成员函数调用方式有什么不同?
非虚成员函数是静态确定的。也就是说,该成员函数(在编译时)被静态地选择,该选
择基
于指象对象的指针(或引用)的类型
相比而言,虚成员函数是动态确定的(在运行时)。也就是说,成员函数(在运行时)
被动态
地选择,该选择基于对象的类型,而不是指向该对象的指针/引用的类型。这被称作“动
态绑
定”。大多数的编译器使用以下的一些的技术:如果对象有一个或多个虚函数,编译器
将一个
隐藏的指针放入对象,该指针称为“virtual-pointor”或“v-pointer”。这个 v-poi
nter
指向一个全局表,该表称为“虚函数表(virtural-table)”或“v-table”。
编译器为每个含有至少一个虚函数的类创建一个 v-table。例如,如果 cirle 类有虚函

draw()、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等内容,也没有涉及诸如页错,通过指
向函数的指
针调用函数等空间/时间论的内容,所以以上讨论是相当简单的。如果你想知道其他的内
容,请询
问 comp.lang.c++;而不要给我发e-mail
----------------------------------------------------------------------------
----
[20.4] 析构函数何时该时虚拟的?
当你可能通过基类指针删除派生类对象时。
虚函数绑定到对象的类的代码,而不是指针/引用的类。如果基类有虚析构函数,你删除
 baseptr
(译注:即基类指针),*baseptr 的对象类型的析构函数被调用,而不是该指针的类型
的析构
函数。这通常是一件好事情。
<--techno-geek 警告;带上头盔。-->
从技术上来说,如果你打算允许其他人通过基类指针调用对象的析构函数(通过delete
这样做
是正常的),并且被析构的对象是有重要的析构函数的派生类的对象,就需要让基类的
析构函
数成为虚拟的。如果一各类有显式的析构函数,或者有成员对象,该成员对象或基类有
重要的
析构函数,那么这个类就有重要的析构函数。(注意这是一个递归的定义(例如,某个
具有重
要析构函数的类,它有一个成员对象(它有基类(该基类有成员对象(它有基类(该基
类有显
式的析构函数))))))
<--techno-geek 警告结束;脱下头盔-->
如果你对以上的规则理解有困难,试试这个简单的:类应该有虚析构函数,除非这个类
没有虚
函数。基本原理:如果根本没有虚函数,你可能将通过基类指针填充派生对象,并且并
且被填
充的一些你可以做,可能包含了析构函数(通过delete的做法是正常的)。一旦你在类
中加上
了一个虚函数,你就已经需要为没一个对象支付空间代价(每个对象一个指针;注意那
是理论
上的编译器细节;实际上没一个都同样做的非常出色),所以使析构函数成为虚拟的通
常不会
额外付出什么。
----------------------------------------------------------------------------
----
[20.5] 什么是“虚构造函数”?
一种允许你做一些 c++ 不直接支持的事情的语法。
你可能通过虚成员函数 clone()(为拷贝构造函数)或虚成员函数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() 调用cir
cle的
默认构造函数。
用户将它们看作“虚构造函数”来使用它们:
    void usercode(shape& s)
    {
      shape* s2 = s.clone();
      shape* s3 = s.create();
      // ...
      delete s2;    // 在此处,你可能需要虚析构函数
      delete s3;
    }
这个函数将正确工作,而不管 shape 是一个 circle,square,或是其他种类的 shape
,甚
至它们还并不存在。

--
                            既然热爱生命
                            那么,
                            一切都在意料之中。

※ 来源:·荔园晨风BBS站 bbs.szu.edu.cn·[FROM: 192.168.28.16]


[回到开始] [上一篇][下一篇]

荔园在线首页 友情链接:深圳大学 深大招生 荔园晨风BBS S-Term软件 网络书店