類別分解:從抽象到具體的層層細化
繼承本質是**結構擴張與特性分解**:基類定義共同協議,派生類逐步具體化差異點。
// 交通工具層次
class Vehicle { // 抽象概念:速度、載重
float speed; int total;
public:
Vehicle(float s, int t) : speed(s), total(t) {}
virtual void Show() = 0; // 純虛函數:強制派生類實現
};
class Car : public Vehicle { // 具體化:增加空氣排量
int airDisp;
public:
Car(int ad, float s, int t) : Vehicle(s, t), airDisp(ad) {}
void Show() override { cout << speed << "|" << total << "|" << airDisp; }
};
class Plane : public Vehicle { // 另一種具體化:增加飛行高度
int altitude;
public:
Plane(int alt, float s, int t) : Vehicle(s, t), altitude(alt) {}
void Show() override { cout << speed << "|" << total << "|" << altitude; }
};
純虛函數與抽象類:強制介面契約
語法與規則
class AbstractBase {
public:
virtual void Interface() = 0; // 純虛函數:無實現、=0 標記
virtual ~AbstractBase() {} // 虛解構函數:刪除基類指標時正確調用派生解構
};
// 錯誤:AbstractBase obj; // 編譯錯誤:抽象類不能實例化
// 正確:AbstractBase *ptr = new Derived();
核心作用
- 語意標記:明確告知「此類為介面/協議,不可直接使用,必須繼承實現」。
- 編譯期強制:派生類**必須**覆寫所有純虛函數,否則派生類仍為抽象類。
- 多態基石:基類指標/引用統一調用派生類實現,實現運行期綁定。
純虛函數的三種進階用法
1. 純虛解構函數:抽象類必備
class Base {
public:
virtual ~Base() = 0; // 純虛解構
};
Base::~Base() {} // **必須提供定義**,因為派生類解構時會調用基類解構
// 若無定義 → 連結錯誤
2. 純虛函數提供預設實現:模板方法模式
class Algorithm {
public:
void Run() { Step1(); Step2(); Step3(); } // 非虛:固定流程
private:
virtual void Step1() = 0; // 子類必須實現
virtual void Step2() { cout << "Default Step2"; } // 虛函數有預設實現
virtual void Step3() = 0;
};
3. 介面類別 (Interface Class):純虛函數集合
class ISerializable { // 命名慣例:I 前綴
public:
virtual ~ISerializable() {}
virtual void Save(ostream&) const = 0;
virtual void Load(istream&) = 0;
};
class Document : public ISerializable { /* 實現序列化 */ };
class Image : public ISerializable { /* 實現序列化 */ };
void SerializeAll(const vector& objs, ostream& out) {
for (auto p : objs) p->Save(out); // 統一介面,多態調用
}
抽象類 vs 介面類別 vs 具體類
| 類別類型 | 純虛函數 | 資料成員 | 構造函數 | 可實例化 | 典型用途 |
| 介面類別 | 全部 | 無 (或僅 static const) | 預設/保護 | 否 | 定義契約、多重實現、插件架構 |
| 抽象基類 | 部分 | 可有 (共享狀態) | 保護 | 否 | 提供通用實現、模板方法、部分介面 |
| 具體類 | 無 | 任意 | 公有 | 是 | 最終產品、值型別 |
現代 C++ 介面設計最佳實踐
- 介面類別盡量純粹:無資料成員、無建構函數參數、虛解構函數、所有方法純虛。
- 基類解構函數必須 virtual (public 繼承時),否則
delete basePtr 僅調用基類解構 → 資源洩漏。
- 用
override (C++11) 明確標記覆寫意圖,編譯器檢查簽名匹配。
- 用
final (C++11) 禁止進一步覆寫/繼承,優化虛函數調用 (devirtualization)。
- 傾向組合而非繼承:
class Car { Engine engine; } 優於 class Car : public Engine。
- 依賴倒置原則 (DIP)**:高層模組依賴抽象介面,低層模組實現介面,雙方不依賴具體類。
常見誤區對比
| 誤區 | 正確理解 |
| 純虛函數不能有函數體 | **可以有** (如純虛解構函數),但派生類仍需覆寫 (解構函數除外,自動鏈式調用) |
| 抽象類不能有構造函數 | **可以有** (通常為 protected),供派生類構造時初始化基類部分 |
| 介面類別應定義為 struct | 慣例用 class + public:,語意更清晰;struct 成員預設 public 易誤用 |
| 多重繼承介面類別會有菱形問題 | 介面類別**無資料成員**,菱形繼承不產生重複資料,**無需虛擬繼承** |
總結
- 類別分解是**由抽象到具體**的層層細化,純虛函數是標記「必須由派生類實現」的編譯期契約。
- 抽象類 (含純虛函數) **不可實例化**,僅作為基類指標/引用的靜態型別。
- 介面類別 (全純虛、無資料) 是現代 C++ 多態設計的核心,支援多重實現、插件架構、依賴倒置。
- 虛解構函數是**多態刪除的生命線**,抽象類必須定義 (哪怕是純虛)。
- 善用
override/final、組合優於繼承、DIP 原則,構建可維護、可擴展、可測試的類別體系。