荔园在线

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

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


发信人: Second (石开), 信区: Program
标  题: C++语言常见问题解答(2-13)抽象化(abstraction)
发信站: 荔园晨风BBS站 (Sun Sep 23 10:51:59 2001), 转信

======================================
■□ 第13节:抽象化(abstraction)
======================================

Q74:分离介面与实作是做什麽用的?

介面是企业体最有价值的资源。设计介面会比只把一堆独立的类别拼凑起来来得耗时

,尤其是:介面需要花费更高阶人力的时间。

既然介面是如此重要,它就应该保护起来,以避免被资料结构等等实作细节之变更所

影响。因此你应该将介面与实作分离开来。

========================================

Q75:在 C++ 里,我该怎样分离介面与实作(像 Modula-2 那样)?

用 ABC(见下一则 FAQ)。

========================================

Q76:ABC ("abstract base class") 是什麽?

在设计层面,ABC 对应到抽象的概念。如果你问机械师父说他修不修运输工具,他可

能会猜你心中想的到底是“哪一种”运输工具,他可能不会修理太空梭、轮船、脚踏

车、核子潜艇。问题在於:「运输工具」是个抽象的概念(譬如:你建不出一辆「运

输工具」,除非你知道要建的是“哪一种”)。在 C++,运输工具类别可当成是一个

ABC,而脚踏车、太空梭……等等都当做它的子类别(轮船“是一种”运输工具)。
在真实世界的 OOP 中,ABC 观念到处都是。

在程式语言层面,ABC 是有一个以上纯虚拟成员函数(pure virtual)的类别(详见

下一则 FAQ),你无法替一个 ABC 建造出物件(案例)来。

========================================

Q77:「纯虚拟」(pure virtual) 成员函数是什麽?

ABC 的某种成员函数,你只能在衍生的类别中实作它。

有些成员函数只存於观念中,没有任何实质的定义。譬如,假设我要你画个 Shape,

它位於 (x,y),大小为 7。你会问我「我该画哪一种 shape?」(圆、方、六边……

都有不同的画法。)在 C++ 里,我们可以先标出有一个叫做 "draw()" 这样的运作

行为,且规定它只能(逻辑上)在子类别中定义出来:

        class Shape {
        public:
          virtual void draw() const = 0;
          //...                     ^^^--- "
= 0" 指:它是 "pure virtual"
        };

此纯虚拟函数让 "Shape" 变成一个 ABC。若你愿意,你可以把 "= 0" 语法想成
是:
该程式码是位於 NULL 指标处。因此,"Shape" 提供一个服务项目,但它现在尚无法

提供实质的程式码以实现之。这样会确保:任何由 Shape 衍生出的 [具体的] 类别

之物件,“将会”有那个我们事先规定的成员函数,即使基底类别尚无足够的资讯去

真正的“定义”它。

【译注】此处「定义」、「宣告」二词要分辨清楚!

========================================

Q78:怎样替整个类别阶层提供列印的功能?

提供一个 friend operator<< 去呼叫 protected 的虚拟函数:

        class Base {
        public:
          friend ostream& operator<< (ostream& o, const Base
& b)
            { b.print(o); return o; }
          //...
        protected:
          virtual void print(ostream& o) const;  //或 "=0;"
 若 "Base" 是个 ABC
        };

        class Derived : public Base {
        protected:
          virtual void print(ostream& o) const;
        };

这样子所有 Base 的子类别只须提供它们自己的 "print(ostream&) const" 成员

数即可(它们都共用 "<<" operator)。这种技巧让夥伴像是有了动态系结的能力。


========================================

Q79:何时该把解构子弄成 virtual?

当你可能经由基底的指标去 "delete" 掉衍生的类别时。

虚拟函数把某物件所属之真正类别所附的程式码,而非该指标/参考本身之类别所附

的程式给系结上去。 当你说 "delete basePtr",且它的基底有虚拟解构子的话,则

真正会被呼叫到的解构子,就是 *basePtr 物件之型态所属的解构子,而不是该指标

本身之型态所附的解构子。一般说来这的确是一件好事。

让你方便起见,你唯一不必将某类别的解构子设为 virtual 的场合是:「该类别“

没有”任何虚拟函数」。因为加入第一个虚拟函数,就会替每个物件都添加额外的空

间负担(通常是一个机器 word 的大小),这正是编译器实作出动态系结的□密;它

通常会替每个物件加入额外的指标,称为「虚拟指标表格」(virtual table pointer
)
,或是 "vptr" 。

========================================

Q80:虚拟建构子 (virtual constructor) 是什麽?

一种让你能做些 C++ 不直接支援的事情之惯用法。

欲做出虚拟建构子的效果,可用个虚拟的 "createCopy()" 成员函数(用来做为拷贝

建构子),或是虚拟的 "createSimilar()" 成员函数(用来做为预设建构子)。

        class Shape {
        public:
          virtual ~Shape() { }          //详见 "virt
ual destructors"
          virtual void draw() = 0;
          virtual void move() = 0;
          //...
          virtual Shape* createCopy() const = 0;
          virtual Shape* createSimilar() const = 0;
        };

        class Circle : public Shape {
        public:
          Circle* createCopy()    const { return new Circ
le(*this); }
          Circle* createSimilar() const { return new Circle(
); }
          //...
        };

执行了 "Circle(*this)" 也就是执行了拷贝建构的行为(在这些运作行为中,
"*this" 的型态为 "const Circle&")。"createSimilar()" 亦类似,但它乃建构

一个“预设的”Circle。

这样用的话,就如同有了「虚拟建构子」(virtual constructors):

        void userCode(Shape& s)
        {
          Shape* s2 = s.createCopy();
          Shape* s3 = s.createSimilar();
          //...
          delete s2;    // 该解构子必须是 virtual 才行!!

          delete s3;    // 如上.
        }

不论该 Shape 是 Circle、Square,甚或其他还不存在的 Shape 种类,这函数都

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

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


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

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