1989:Cfront 2.0——多重繼承與抽象類別#
事件#
1989 年,Cfront 2.0 發佈,C++ 語言大幅擴充。
關鍵語法/特性#
- 多重繼承(multiple inheritance):一個類別可繼承多個基底類別。
- 抽象類別(abstract class):含
virtual純虛擬函式(= 0)的類別不可實例化——純介面。 const成員函式:void f() const;保證不修改物件狀態。static成員:屬於類別而非物件的成員。- 成員指標:
int X::*p。
為何重要#
- 多重繼承:讓「一個類別同時具備多種能力」可以靜態組合,但也帶來菱形繼承(diamond problem)的複雜性,最終在 1991 年以 虛擬繼承(Cfront 3.0)補救。
- 抽象類別:確立「介面與實作分離」的語言層面支援——COM、CORBA 等元件模型都建立在純虛擬函式上。
- const 成員函式:把「唯讀性」寫進型別系統,是 C++ const 正確性(const correctness) 的核心。
理論與實用原因#
- 理論原因:多重繼承源自 Synthesizer Generator 與 Eiffel 等研究;抽象類別源自 subtype 理論。
- 實用原因:大型專案需要「多介面實作」(如「可列印」+「可序列化」同時存在於一個類別)。
彌補了什麼缺陷#
彌補了 C++ 1.0「單一繼承無法表達多重能力」與「無法在語言層面定義純介面」的缺陷;也彌補了 C 的 const 只是「唯讀修飾詞」而非型別系統一部分的缺陷。
程式範例#
舊寫法(C++ 1.0,沒有抽象類別):只能靠慣例提供虛擬函式,但基底類別仍可被實例化,忘了改寫也不會在編譯期報錯:
class Drawable { // 只是「慣例上的介面」
public:
virtual void draw(); // 有預設實作,忘了 override 不會報錯
};
Drawable d; // 竟然可以實例化一個「介面」!
新寫法(1989,抽象類別 + 純虛擬函式 = 0):語言層面強制介面契約:
class Drawable { // 抽象類別:純介面
public:
virtual void draw() const = 0; // 純虛擬函式,不可有實作
virtual ~Drawable() {}
};
// Drawable d; // 編譯錯誤:抽象類別不可實例化
class Shape : public Drawable { // 忘了實作 draw() 就無法實例化
int x, y;
public:
Shape(int x_, int y_) : x(x_), y(y_) {}
void draw() const override { // const 成員函式:保證不修改物件狀態
std::cout << "Shape at (" << x << "," << y << ")\n";
}
};
多重繼承:一個類別同時繼承兩個介面,靜態組合多種能力:
class Printable { // 介面一:可列印
public:
virtual void print() const = 0;
virtual ~Printable() {}
};
class Serializable { // 介面二:可序列化
public:
virtual void save(const char* file) const = 0;
virtual ~Serializable() {}
};
class Document : public Printable, public Serializable { // 同時具備兩種能力
public:
void print() const override { /* ... */ }
void save(const char* file) const override { /* ... */ }
};
static 成員:屬於類別而非物件,適合計數器、共用設定:
class Document {
public:
static int count; // 宣告:所有物件共用
Document() { count++; }
~Document() { count--; }
};
int Document::count = 0; // 1989 年仍需在類別外定義