前言:同上文一樣,本文源於對另一位朋友的問題的解答
(參見文章http://community.csdn.net/Expert/topic/3202/3202729.xml?temp=5.602664E-02)
C++不是動態語言,所以沒法從語言機制上實作類別的動態建立,但這樣的需求卻有可能存在,一個類似的例子便是MFC中CWnd類的Create方法,其第一個參數為Window Class的名字,這就允許使用者通過class的名字來建立相應的視窗。
要想實現這一點,必須有一個“管理中心”,用於登記類的名字,並且通過名字能夠調用某個方法來建立相應的類。結合類工廠的設計思想,這裡我們讓一套繼承體系中的基類作為“管理中心”,由它來維護所有衍生類別的必要資訊,包括類名和工廠函數,這二者必須建立起映射關係,map是不錯的選擇。定義了一個衍生類別後,它就自動向基類進行註冊,可是如何?自動呢?先看看程式吧:
// 為編寫方便,下面的代碼都位於.cpp檔案中
#ifdef _MSC_VER
#pragma warning(disable: 4786)
#endif
#include <iostream>
#include <map>
using namespace std;
class Base
{
public:
virtual void Print() // 測試用
{
cout << "This is Base" << endl;
}
protected:
typedef Base* (*ClassGen)(); // 聲明函數指標
static void Register(const char* class_name, ClassGen class_gen) // 註冊函數
{
class_set.insert(map<const char*, ClassGen>::value_type(class_name, class_gen));
}
public:
static Base* Create(const char* class_name) // 工廠函數
{
map<const char*, ClassGen>::iterator iter;
if((iter = class_set.find(class_name)) != class_set.end())
{
return ((*iter).second)();
}
return NULL;
}
protected:
static map<const char*, ClassGen> class_set; // 儲存子類資訊
};
map<const char*, Base::ClassGen> Base::class_set; // 靜態成員變數定義
class Derived : public Base
{
public:
struct DerivedRegister // 輔助類,用於註冊
{
DerivedRegister()
{ // 註冊子類,雖然map能保證唯一,但仍只註冊一次
static bool bRegistered = false;
if(!bRegistered)
{
Base::Register("Derived", Derived::Create); // 註冊子類資訊
bRegistered = true;
}
}
};
static Base* Create() // 工廠函數
{
return new Derived;
}
public:
virtual void Print() // 測試用
{
cout << "This is Derived" << endl;
}
};
static Derived::DerivedRegister Derived_for_registering; // 沒有其它機制能夠保證在全域空間調用註冊函數,
// 不得已,定義一個全域變數來完成這項光榮的任務,看起來有點齷齪
int main()
{
Base* pDerived = Base::Create("Derived"); // 類名可以動態輸入
if(pDerived) pDerived->Print(); // 建立成功調用虛函數
else cout << "Create error" << endl;
system("pause");
return 0;
}
上面這樣實現,有的朋友可能覺得使用起來很麻煩,實際上我們用宏定義來改造一下就會非常漂亮,代碼呆會兒就會看到。還是說說自動註冊功能吧,首先,為什麼要實現自動註冊呢?這是為了最大程度上隱藏實現,實際上採用手動註冊也可以,只不過我們得在主函數裡一一調用每個類的註冊函數。那我們是如何?自動註冊的呢?方法是將註冊代碼放到一個輔助類的建構函式中,然後定義一個該類的靜態全域變數,這樣建構函式便被調用了,:),缺點是多了一個額外的對象,除了用於註冊外,它什麼用也沒有(最初我沒有採用輔助類,而是直接在衍生類別的建構函式中註冊,然後定義衍生類別的全域變數,這樣明顯會浪費空間,採用輔助類便將額外開銷降低到了最小)。
下面讓我們瞧瞧用宏改造後的模樣:
#ifdef _MSC_VER
#pragma warning(disable: 4786)
#endif
#include <iostream>
#include <map>
using namespace std;
// 用於聲明具有動態建立功能的基類
#define DECLARE_DYNCRT_BASE(base) /
public: /
typedef base* (*ClassGen)(); /
static void Register(const char* class_name, ClassGen class_gen) /
{ /
class_set.insert(map<const char*, ClassGen>::value_type(class_name, class_gen)); /
} /
public: /
static base* Create(const char* class_name) /
{ /
map<const char*, ClassGen>::iterator iter; /
if((iter = class_set.find(class_name)) != class_set.end()) /
{ /
return ((*iter).second)(); /
} /
return NULL; /
} /
protected: /
static map<const char*, ClassGen> class_set;
// 用於實現基類
#define IMPLEMENT_DYNCRT_BASE(base) /
map<const char*, base::ClassGen> base::class_set;
// 用於聲明一個能夠被動態建立的類
#define DECLARE_DYNCRT_CLASS(derived, base) /
public: /
struct derived##Register /
{ /
derived##Register() /
{ /
static bool bRegistered = false; /
if(!bRegistered) /
{ /
base::Register(#derived, Create); /
bRegistered = true; /
} /
} /
}; /
static base* Create() /
{ /
return new derived; /
}
// 用於實現一個能夠被動態建立的類
#define IMPLEMENT_DYNCRT_CLASS(derived) /
static derived::derived##Register derived##_for_registering;
// 測試
class Base
{
DECLARE_DYNCRT_BASE(Base) // 聲明動態基類
DECLARE_DYNCRT_CLASS(Base, Base) // 基類自己也可以動態建立
public:
virtual void Print()
{
cout << "This is Base" << endl;
}
};
IMPLEMENT_DYNCRT_BASE(Base) // 實現動態基類
IMPLEMENT_DYNCRT_CLASS(Base) // 實現動態類
class Derived : public Base
{
DECLARE_DYNCRT_CLASS(Derived, Base) // 聲明動態類
public:
virtual void Print()
{
cout << "This is Derived" << endl;
}
};
IMPLEMENT_DYNCRT_CLASS(Derived) // 實現動態類
int main()
{
Base* pBase = Base::Create("Base"); // 類名可以動態輸入
if(pBase) pBase->Print(); // 建立成功調用虛函數
else cout << "Create Base error" << endl;
Base* pDerived = Base::Create("Derived"); // 類名可以動態輸入
if(pDerived) pDerived->Print(); // 建立成功調用虛函數
else cout << "Create Derived error" << endl;
system("pause");
return 0;
}
上面的宏定義可以重複利用(是不是有點似曾相識,是的,MFC中在對序列化和動態建立等支援時用到了類似的宏),在使用的時候,只需簡單的使用這四個宏,即可實現動態建立,感覺還不錯吧,不過提醒一點,其中的兩個IMPLEMENT宏都應該放在.cpp檔案中。
這種方法還是存在一些缺點的,不過本文主要是提供一種思路,可以根據具體的情況再作相應變動。
(freefalcon於2004.09.19)