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

C++ 實作 .NET 風格委派:類型安全的回呼機制完整指南

     編輯:關於C++

前言:為什麼 C++ 需要委派機制?

在 .NET 框架中,委派是實現事件驅動程式設計的核心機制。它允許類別註冊事件處理器,當事件觸發時自動呼叫已註冊的方法。然而在標準 C++ 中,特別是處理非靜態成員函式作為回呼函式時,卻面臨著類型安全與物件上下文綁定的雙重挑戰。本文將詳細介紹一種完整的解決方案,透過函數物件與巨集模板技術,實現支援靜態成員函式、非靜態成員函式、全域函式的類型安全委派系統。

核心概念:什麼是委派?

根據 .NET 定義,委派是一個可持有方法參考的類別,擁有固定的簽章(回傳型別、參數型別與數量),只能引用符合該簽章的方法。本質上,它是一個類型安全的函式指標。提供委派的類別允許外部在委派上註冊處理函式;當委派被呼叫時,會依序執行所有已註冊的處理函式,而來源類別無需知悉具體註冊了多少處理器——委派內部自行管理列表與呼叫邏輯。

基礎建構:函數物件

C++ 中實現委派的關鍵在於函數物件。透過重載 operator(),使物件可像函式一樣被呼叫,同時內部封裝物件指標與成員函式指標,解決非靜態成員函式需綁定特定物件實例的問題。

基本函數物件範例

template <class T>
class Functor {
public:
    Functor(T* pObj, int (T::*pFunc)(int)) {
        m_pObject = pObj;
        m_pFunction = pFunc;
    }
    int operator()(int p) {
        return (m_pObject->*m_pFunction)(p);
    }
private:
    T* m_pObject;
    int (T::*m_pFunction)(int);
};

使用方式:

class MyClass {
public:
    int Square(int p) { return p * p; }
};
void some_function() {
    MyClass theClass;
    Functor<MyClass> myFunc(&theClass, &MyClass::Square);
    int result = myFunc(5); // result = 25
}

多型支援:抽象基類與模板衍生類

為了統一管理不同類別的函數物件,引入抽象基類 Functor 與模板衍生類 TemplateFunctor<T>:

class Functor {
public:
    virtual int operator()(int) = 0;
};

template <class T>
class TemplateFunctor : public Functor {
public:
    TemplateFunctor(T* pObj, int (T::*pFunc)(int)) {
        m_pObject = pObj;
        m_pFunction = pFunc;
    }
    int operator()(int p) override {
        return (m_pObject->*m_pFunction)(p);
    }
private:
    T* m_pObject;
    int (T::*m_pFunction)(int);
};

此設計允許以基類指標統一操作各種具體函數物件,實現執行期多型。

巨集參數化:消除重複程式碼

針對不同參數列表與回傳型別,手動撰寫函數物件類別極為繁瑣。利用巨集 DECLARE_FUNCTOR 可自動產生對應的基類與模板類:

#define DECLARE_FUNCTOR(name, parmdecl, parmcall)
class name##Functor {
public:
    virtual void operator() parmdecl = 0;
};
template <class C>
class name##TFunctor : public name##Functor {
public:
    name##TFunctor(C* pObj, void (C::*pFunc)parmdecl) {
        m_pObj = pObj; m_pFunc = pFunc;
    }
    void operator() parmdecl { (m_pObj->*m_pFunc)parmcall; }
private:
    C* m_pObj;
    void (C::*m_pFunc)parmdecl;
};

參數說明:

  • name:函數物件名稱前綴
  • parmdecl:運算子參數宣告(含括號),如 (int p1, int p2)
  • parmcall:實際呼叫參數列表,如 (p1, p2)

範例:DECLARE_FUNCTOR(Add, (int p1, int p2), (p1, p2)) 將展開為處理兩個 int 參數的 AddFunctor 與 AddTFunctor。

委派實作:多播機制

委派在函數物件基礎上進一步封裝,維護一個函數物件指標的動態陣列(std::vector<Functor*>),支援多重回呼:

#define DECLARE_DELEGATE(name, parmdecl, parmcall)
namespace name##Delegate {
    class Delegate {
    public:
        Delegate(); ~Delegate();
        template <class C> void Add(C* pObj, void (C::*pFunc)parmdecl);
        void Add(void (*pFunc)parmdecl); // 靜態/全域函式
        template <class C> void Remove(C* pObj, void (C::*pFunc)parmdecl);
        void Remove(void (*pFunc)parmdecl);
        void operator+=(Functor* pFunc);
        void operator+=(void (*pFunc)parmdecl);
        void operator-=(Functor* pFunc);
        void operator-=(void (*pFunc)parmdecl);
        void Invoke parmdecl;
        void operator() parmdecl { Invoke parmcall; }
    private:
        std::vector<Functor*> m_pFuncs;
        typedef std::vector<Functor*>::iterator vit;
    };
}
template <class C>
name##Delegate::TFunctor<C>* name##Handler(C* pObj, void (C::*pFunc)parmdecl) {
    return new name##Delegate::TFunctor<C>(pObj, pFunc);
}

關鍵設計亮點

  • 命名空間隔離:委派與函數物件置於專屬命名空間,避免名稱衝突。
  • 統一儲存:vector 儲存基類指標,可容納任意具體類型的函數物件。
  • 雙重介面:提供 Add/Remove 與 +=/-= 兩組等效操作,並支援成員函式與非成員函式重載。
  • 靜態/全域函式支援:額外定義 FFunctor 封裝普通函式指標,無需物件指標。
  • 記憶體管理:解構函式自動釋放所有函數物件記憶體。

完整使用範例

DECLARE_DELEGATE(Add, (int p1, float p2), (p1, p2))

class A {
public:
    A() : value(5) {}
    virtual void Fun1(int val, float val2) {
        value = val * 2 * (int)val2;
        cout << "[A::Fun1] " << val << ", " << val2 << endl;
    }
    static void StaticFunc(int val, float val2) {
        cout << "[A::StaticFunc] " << val << ", " << val2 << endl;
    }
    int value;
};

class B : public A {
public:
    void Fun1(int val, float val2) override {
        value += val * 3 * (int)val2;
        cout << "[B::Fun1] " << val << ", " << val2 << endl;
    }
};

void GlobalFunc(int val, float val2) {
    cout << "[GlobalFunc] " << val << ", " << val2 << endl;
}

int main() {
    A a; B b;
    AddDelegate::Delegate del;

    // 註冊處理器
    del += AddHandler(&a, &A::Fun1);
    del += AddHandler(&b, &B::Fun1);
    del += GlobalFunc;
    del += A::StaticFunc;

    // 觸發委派
    del(4, 5); // 依序呼叫四個處理器

    // 移除部分處理器後再次觸發
    del -= AddHandler(&a, &A::Fun1);
    del -= A::StaticFunc;
    del(4, 5);

    return 0;
}

原始碼完整實作

完整的 delegate.h 包含上述所有巨集與類別定義,並處理了 STL 警告抑制、記憶體管理、比較運算子等細節。核心邏輯包含:

  • Functor 抽象基類定義 Invoke、isMethod、operator==
  • TFunctor<C> 實作成員函式綁定與比較
  • FFunctor 實作靜態/全域函式封裝
  • Delegate 管理 vector<Functor*>,提供增刪查與呼叫
  • 輔助函式 name##Handler 簡化函數物件建立

總結與展望

此實現展示了如何在 C++ 中以模板、巨集與多型技術,構建出類似 .NET 的類型安全委派系統。雖然現代 C++ (C++11 起) 提供了 std::function、std::bind、Lambda 表達式等更強大的標準工具,但理解這套經典實作的原理,對於掌握 C++ 物件模型、函式指標與模板元程式設計仍具備重要教學價值。未來可延伸方向包括:支援非 void 回傳值並收集結果、固定參數個數的特化版本、以及與現代 C++ 標準庫的互操作。

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