首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> C++ >> 關於C++

C++ 抽象機制深度探討:從虛函數到模板代理模式

     編輯:關於C++

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 { /* ... */ }
};

三大核心限制

  1. 血緣綁定:被抽象類別必須直接/間接繼承自共同基類
  2. 介面前置:衍生類需抽象的操作必須在基類宣告 (除非用不安全的向下轉型)
  3. 虛擬解構函數:基類解構函數必須為 virtual,否則刪除基類指標時行為未定義

二、實務困境與動機

  • 二次開發:拿到一堆 DLL、Header、Doc,類別之間無繼承關係,程式充滿 switch/if-else 分派
  • MFC 擴充難:從 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 擴充)
  • 單一物件註冊多個方法
  • 介面與實作完全解耦

四、方案比較與選型建議

方案適用場景優點缺點
標準虛函數繼承自有程式碼、設計階段可控制類別體系語言原生支援、效能最佳、工具鏈完善需共同基類、侵入式設計
模板代理 (固定介面)第三方類別、無虛函數類別、需統一固定介面零侵入、型別安全、編譯期多型介面名稱固定、每個介面需寫代理類
模板代理 (函數指標註冊)需動態綁定不同方法、整合多種異構類別極大彈性、介面與實作完全分離語法較複雜、函數指標調用微小開銷
外觀模式/適配器類別封裝複雜子系統、提供簡化介面意圖清晰、易維護手工編寫樣板碼多

五、現代 C++ 補充

  • C++11 std::function + std::bind/Lambda 可替代成員函數指標,語法更友善
  • C++20 Concepts 可在編譯期約束代理模板參數需滿足的介面
  • std::variant + std::visit 提供另一種無繼承多型方案

結語

C++ 抽象的「欄桿」(繼承、虛函數、虛擬解構函數) 非阻礙,乃護欄,引導邏輯沿正確方向前行。標準繼承多型為首選;面對離散、不可修改、跨庫類別時,模板代理模式提供強大的補充武器。工程實務中,應根據擁有權、效能、維護性綜合選型,而非教條主義。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved