| 
 Q64:"public:"、"private:"、"protected:" 的差别是?   
   
"Private:" 在前几节中讨论过了;"public:" 是指:「任何人都能存取之」;第三   
个 "protected:" 是让某成员(资料成员或是成员函数)只能由衍生类别存取之。   
   
【译注】"protected:" 是让「衍生类别」,而非让「衍生类别的物件案例」能存取   
        得到 protected 的部份。   
   
========================================   
   
Q65:当我改变了内部的东西,怎样避免子类别被破坏?   
   
物件类别有两个不同的介面,提供给不同种类的用户:   
 * "public:" 介面用以服务不相关的类别。   
 * "protected:" 介面用以服务衍生的类别。   
   
除非你预期所有的子类别都会由你们的工作小组建出来,否则你应该将基底类别的资   
料位元内容放在 "private:" 处,用 "protected:" 行内存取函数来存取那些资料。   
这样的话,即使基底类别的私有资料改变了,衍生类别的程式也不会报废,除非你改   
变了基底类别的 protected 处的存取函数。   
   
================================   
● 12D:继承--建构子与解构子   
================================   
   
Q66:若基底类别的建构子呼叫一个虚拟函数,为什麽衍生类别覆盖掉的那个虚拟函   
     数却不会被呼叫到?   
   
在基底类别 Base 的建构子执行过程中,该物件还不是属於衍生 Derived 的,所以   
如果 "Base::Base()" 呼叫了虚拟函数 "virt()",则 "Base::virt()" 会被呼叫,   
即使真的有 "Derived::virt()"。   
   
类似的道理,当 Base 的解构子执行时,该物件不再是个 Derived 了,所以当   
Base::~Base() 呼叫 "virt()",则 "Base::virt()" 会被执行,而非覆盖後的版本   
"Derived::virt()"。   
   
当你想像到:如果 "Derived::virt()" 碰得到 Derived 类别的物件成员,会造成什   
麽样的灾难,你很快就会看出这规则的明智之处。   
   
================================   
   
Q67:衍生类别的解构子应该外显地呼叫基底的解构子吗?   
   
不要,绝对不要外显地呼叫解构子(「绝对不要」指的是「几乎完全不要」)。   
   
衍生类别的解构子(不管你是否明显定义过)会“自动”去呼叫成员物件的、以及基   
底类别之子物件的解构子。成员物件会以它们在类别中出现的相反顺序解构,接下来   
是基底类别的子物件,以它们出现在类别基底列表的相反顺序解构之。   
   
只有在极为特殊的情况下,你才应外显地呼叫解构子,像是:解构一个由「新放入的   
new 运算子」配置的物件。   
   
===========================================   
● 12E:继承--Private 与 protected 继承   
===========================================   
   
Q68:该怎麽表达出「私有继承」(private inheritance)?   
   
用 ": private" 来代替 ": public."  譬如:   
   
        class Foo : private Bar {   
          //...   
        };   
   
================================   
   
Q69:「私有继承」和「成份」(composition) 有多类似?   
   
私有继承是「成份」(has-a) 的一种语法变形。   
   
譬如:「汽车有引擎」("car has-a engine") 关系可用成份来表达:   
   
        class Engine {   
        public:   
          Engine(int numCylinders);   
          void start();                 //starts this Engine   
        };   
   
        class Car {   
        public:   
          Car() : e_(8) { }             //initializes this Car with 8 cylinders   
          void start() { e_.start(); }  //start this Car by starting its engine   
        private:   
          Engine e_;   
        };   
   
同样的 "has-a" 关系也可用私有继承来表达:   
   
        class Car : private Engine {   
        public:   
          Car() : Engine(8) { }         //initializes this Car with 8 cylinders   
          Engine::start;                //start this Car by starting its engine   
        };   
   
这两种型式的成份有几分相似性:   
 * 这两种情况之下,Car 只含有一个 Engine 成员物件。   
 * 两种情况都不能让(外界)使用者由 Car* 转换成 Engine* 。   
   
也有几个不同点:   
 * 如果你想要让每个 Car 都含有数个 Engine 的话,就得用第一个型式。   
 * 第二个型式可能会导致不必要的多重继承(multiple inheritance)。   
 * 第二个型式允许 Car 的成员从 Car* 转换成 Engine* 。   
 * 第二个型式可存取到基底类别的 "protected" 成员。   
 * 第二个型式允许 Car 覆盖掉 Engine 的虚拟函数。   
   
注意:私有继承通常是用来获得基底类别 "protected:" 成员的存取权力,但这通常   
只是个短程的解决方案。   
   
========================================   
   
Q70:我比较该用哪一种:成份还是私有继承?   
   
成份。   
   
正常情形下,你不希望存取到太多其他类别的内部,但私有继承会给你这些额外的权   
力(与责任)。不过私有继承不是洪水猛兽;它只是得多花心力去维护罢了,因为它   
增加了别人动到你的东西、让你的程式出差错的机会。   
   
合法而长程地使用私有继承的时机是:当你想新建一个 Fred 类别,它会用到 Wilma   
类别的程式码,而且 Wilma 的程式码也会呼叫到你这个 Fred 类别里的运作行为时   
。这种情形之下,Fred 呼叫了 Wilma 的非虚拟函数,Wilma 也呼叫了它自己的、会   
被 Fred 所覆盖的虚拟函数(通常是纯虚拟函数)。要用成份来做的话,太难了。   
   
        class Wilma {   
        protected:   
          void fredCallsWilma()   
            { cout << "Wilma::fredCallsWilma()\n"; wilmaCallsFred(); }   
          virtual void wilmaCallsFred() = 0;   
        };   
   
        class Fred : private Wilma {   
        public:   
          void barney()   
            { cout << "Fred::barney()\n"; Wilma::fredCallsWilma(); }   
        protected:   
          virtual void wilmaCallsFred()   
            { cout << "Fred::wilmaCallsFred()\n"; }   
        };   
   
========================================   
   
Q71:我应该用指标转型方法,把「私有」衍生类别转成它的基底吗?   
   
当然不该。   
   
以私有衍生类别的运作行为、夥伴来看,从它上溯到基底类别的关系为已知的,所以   
从 PrivatelyDer* 往上转换成 Base*(或是从 PrivatelyDer& 到 Base&)是安全的   
;强制转型是不需要也不鼓励的。   
   
然而用 PrivateDer 的人应该避免这种不安全的转换,因为此乃立足於 PrivateDer   
的 "private" 决定,这个决定很容易在日後不经察觉就改变了。   
   
========================================   
   
Q72:保护继承 (protected inheritance) 和私有继承有何关连?   
   
相似处:两者都能覆盖掉私有/保护基底类别的虚拟函数,两者都不把衍生的类别视 
 
为“一种”基底类别。   
   
不相似处:保护继承可让衍生类别的衍生类别知道它的继承关系(把实行细节显现出   
来)。它有好处(允许保护继承类别的子类别,藉这项关系来使用保护基底类别),   
也有代价(保护继承的类别,无法既想改变这种关系,而又不破坏到进一步的衍生类   
别)。   
   
保护继承使用 ": protected" 这种语法:   
   
        class Car : protected Engine {   
          //...   
        };   
   
========================================   
   
Q73:"private" 和 "protected" 的存取规则是什麽?   
   
拿底下这些类别当例子:   
   
        class B                    { /*...*/ };   
        class D_priv : private   B { /*...*/ };   
        class D_prot : protected B { /*...*/ };   
        class D_publ : public    B { /*...*/ };   
        class UserClass            { B b; /*...*/ };   
   
没有一个子类别能存取到 B 的 private 部份。   
在 D_priv 内,B 的 public 和 protected 部份都变成 "private"。   
在 D_prot 内,B 的 public 和 protected 部份都变成 "protected"。   
在 D_publ 内,B 的 public 部份还是 public,protected 还是 protected   
 (D_publ is-a-kind-of-a B) 。   
Class "UserClass" 只能存取 B 的 public 部份,也就是:把 UserClass 从 B 那   
儿封起来了。   
   
欲把 B 的 public 成员在 D_priv 或 D_prot 内也变成 public,只要在该成员的名   
字前面加上 "B::"。譬如:想让 "B::f(int,float)" 成员在 D_prot 内也是 public   
的话,照这样写:   
   
        class D_prot : protected B {   
        public:   
          B::f;    //注意:不是写成 "B::f(int,float)"   
        };   
   
   
======================================   
■□ 第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() { }          //详见 "virtual 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 Circle(*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 种类,这函数都能   
正确执行。  |