C++ 的抽象核心在於「用相同行為操作不同物件」。標準抽象依賴繼承體系與虛函數,但實務上常面臨無法修改原始碼、離散類別無共同基類、或需對第三方庫類別統一介面等挑戰。本文系統分析 C++ 抽象的限制,並提出模板代理模式作為補充方案。
class Parent {
public:
virtual ~Parent() {}
virtual void GetValue() = 0;
virtual void Test() = 0;
};
class Child1 : public Parent {
public:
virtual ~Child1() {}
virtual void GetValue() override { /* ... */ }
virtual void Test() const override { /* ... */ } // 注意:const 不同簽名,非重寫
};
class Child2 : public Parent {
public:
virtual ~Child2() {}
virtual void GetValue() override { /* ... */ }
virtual void Test() override { /* ... */ }
};
virtual,否則刪除基類指標時行為未定義switch/if-else 分派CDialog 衍生多個對話框類,若某類新增虛函數,外部無法透過基類指標呼叫,除非在中間插入新類別層次——為單一方法增加一層繼承得不償失#include
class IParent {
public:
virtual ~IParent() {}
virtual void DoSomething() const = 0;
};
template
class ChildProxy : public IParent {
public:
ChildProxy() : m_obj(new T) {}
~ChildProxy() { delete m_obj; }
void DoSomething() const override { m_obj->InitObj(); }
private:
T* m_obj;
};
class Test {
public:
void InitObj() { MessageBox(NULL, L"Test", L"OK", MB_OK); }
};
int main() {
std::list objs;
objs.push_back(new ChildProxy());
for (auto p : objs) p->DoSomething();
// 記得釋放記憶體...
}
優點:型別安全、無需修改原類別、統一介面。
缺點:介面名稱 (DoSomething) 編譯期固定,彈性不足。
class IParent {
public:
virtual ~IParent() {}
virtual void Do1() const = 0;
virtual int Do2(char*) const = 0;
};
template
class ChildProxy : public IParent {
using PFun1 = void (T::*)();
using PFun2 = int (T::*)(char*);
public:
ChildProxy(PFun1 f1) : m_obj(new T), m_f1(f1), m_f2(nullptr) {}
ChildProxy(PFun2 f2) : m_obj(new T), m_f1(nullptr), m_f2(f2) {}
ChildProxy(PFun1 f1, PFun2 f2) : m_obj(new T), m_f1(f1), m_f2(f2) {}
~ChildProxy() { delete m_obj; }
void Do1() const override { if (m_f1) (m_obj->*m_f1)(); }
int Do2(char* p) const override { return m_f2 ? (m_obj->*m_f2)(p) : -1; }
private:
T* m_obj;
PFun1 m_f1;
PFun2 m_f2;
};
class Test {
public:
void Test1() { MessageBox(NULL, L"Test1", L"OK", MB_OK); }
int Test2(char* p) { return strlen(p); }
};
int main() {
std::list objs;
objs.push_back(new ChildProxy(&Test::Test1));
objs.push_back(new ChildProxy(&Test::Test2));
// 統一介面操作...
}
此版本支援:
typedef 擴充)| 方案 | 適用場景 | 優點 | 缺點 |
|---|---|---|---|
| 標準虛函數繼承 | 自有程式碼、設計階段可控制類別體系 | 語言原生支援、效能最佳、工具鏈完善 | 需共同基類、侵入式設計 |
| 模板代理 (固定介面) | 第三方類別、無虛函數類別、需統一固定介面 | 零侵入、型別安全、編譯期多型 | 介面名稱固定、每個介面需寫代理類 |
| 模板代理 (函數指標註冊) | 需動態綁定不同方法、整合多種異構類別 | 極大彈性、介面與實作完全分離 | 語法較複雜、函數指標調用微小開銷 |
| 外觀模式/適配器類別 | 封裝複雜子系統、提供簡化介面 | 意圖清晰、易維護 | 手工編寫樣板碼多 |
std::function + std::bind/Lambda 可替代成員函數指標,語法更友善std::variant + std::visit 提供另一種無繼承多型方案C++ 抽象的「欄桿」(繼承、虛函數、虛擬解構函數) 非阻礙,乃護欄,引導邏輯沿正確方向前行。標準繼承多型為首選;面對離散、不可修改、跨庫類別時,模板代理模式提供強大的補充武器。工程實務中,應根據擁有權、效能、維護性綜合選型,而非教條主義。