轉(zhuǎn)帖請注明出處 http://www.shnenglu.com/cexer/archive/2008/07/05/55419.html
世界上有個叫__uuidof的關(guān)鍵字。這是一個家喻戶曉且其被廣泛使用的關(guān)鍵字,幾乎可以說,有COM程序員的地方,就有它 __uuidof的存在。其很好很強(qiáng)大的程度是人所共見的,夸張一點比喻:離開它的COM程序員,就像失去了點火器的火箭,雖然可以人工點火,但是不安全且無效率。
不過很多人并不知道,這其實是一個編譯器擴(kuò)展關(guān)鍵字,提供了此關(guān)鍵字的僅VC一家別無它店。幸運(yùn)的是,強(qiáng)大的C++讓我們能夠輕易仿真出這個關(guān)鍵字的大部分功能。
網(wǎng)上能夠找到一種仿真的方法,見許式偉:《仿真VC++提供的關(guān)鍵字__uuidof》。該方法的實現(xiàn)是:特化模板類的成員函數(shù),然后運(yùn)行時調(diào)用函數(shù)根據(jù)UUID字符串產(chǎn)生出UUID,由于是生成于運(yùn)行時,所以它無可避免地有兩個缺點:
- 存在運(yùn)行時消耗。
- 無法作為非類型模板參數(shù)傳遞給模板。
那些整天流著口水追求效率的C++程序員們,是不能忍受任何不必要的運(yùn)行時消耗的。對于第二點,VC的關(guān)鍵字__uuidof取出來的UUID是能夠作為非類型模板參數(shù)傳遞的,ATL中就大量地使用了這樣的參數(shù)傳遞形式,所以目前的這種實現(xiàn)功能有限,仿真度還不夠高。
其實只要能讓它能夠編譯期決定UUID的值,那么這兩個問題就迎刃而解了。而這是肯定可以實現(xiàn)的,并且很簡單。我曾經(jīng)在自己寫的一個COM庫里實現(xiàn)過這樣的方法,雖然那個庫已經(jīng)不知丟到哪里去了,不過那個方法還記得。
解決的途徑還是離不開模板特化。類的成員包括成員函數(shù)和成員變量,函數(shù)是運(yùn)行時作用的,然而static const的成員變量可以是編譯期就決定。所以解決的方法就在眼前了:特化模板的成員變量。
以下是我的實現(xiàn)方法。
先定義一個類模板,它有一個static const ,UUID類型的成員變量:
template<typename T>
struct _uuid_of_impl
{
static const UUID id;
};
template<typename T>
const UUID _uuid_of_impl<T>::id=GUID_NULL;
有了這個簡單的東西就好辦了,只需要針對某個接口特它的成員變量就行了,如:
template<>
const UUID _uuid_of_impl<IUnknown>::id=IID_IUnknown;
template<>
const UUID _uuid_of_impl<IDispatch>::id=IID_IDispatch;
然后我們就可以這樣取得接口的UUID:
IID IunknownID=_uuid_of_impl<IUnknown>::id;
IID IdispatchID=_uuid_of_impl<IDispatch>::id;
作為非類型模板參數(shù)傳遞:
template<const IID* t_iid>
struct __uuid_of_test
{
__uuid_of_test()
{}
void test()
{
t_iid;
}
};
__uuid_of_test<&(_uuid_of_impl<IDispatch>::id) > obj;
不過現(xiàn)在這種實現(xiàn)還有一些問題,看以下代碼:
IID ITypelibID=_uuid_of_impl<ITypeLib>::id;
注意我們并沒有事先對模板__uuid_of_impl特化ITypeLib的版本。但是以上語句卻能夠編譯通過,在運(yùn)行時,__uuid_of_impl<ITypeLib>的值將會是錯誤的值GUID_NULL。這是因為,我們定義模板的時候,同時在模板外定義了模板的靜態(tài)成員變量并賦值為GUID_NULL,所以沒有用特化的方法定義UUID的接口,都將使用GUID_NULL這個通用值。這當(dāng)然不是我們想要的。所以我們想在沒有定義UUID的時候讓編譯器警告我們,要達(dá)到這樣的效果只需要去掉上面那句:
template<typename T>
const UUID _uuid_of_impl<T>::id=GUID_NULL;
現(xiàn)在再進(jìn)行編譯,編譯器會告訴你,有一個無法解析的符號。根據(jù)編譯器提供的相關(guān)信息,很容易就能確定問題所在。這樣能夠在編譯期極大地減小安全隱患。
最后加上我們定義的幾個宏,這是最后的全部實現(xiàn):
template<typename T>
struct _uuid_of_impl
{
static const UUID id;
};
#define uuid_of(x) _uuid_of_impl<x>::id
#define DEFINE_UUID(x,l,w1,w2,b1,b2,b3,b4,b5,b6,b7,b8) \
template<> \
const UUID _uuid_of_impl<x>::id={l,w1,w2,{b1,b2,b3,b4,b5,b6,b7,b8}}
用以下代碼測試通過:
struct ITest{};
DEFINE_UUID(ITest,0x96289151,0xf059,0x4049,0x88,0x19,0x61,0xa6,0xe9,0x79,0xc,0xf1);
template<const IID* t_iid>
struct uuid_of_test
{
uuid_of_test(){}
};
int main()
{
IID xxxxID=uuid_of(ITest);
uuid_of_test<&(uuid_of(ITest))> obj;
return 0;
}
需要注意的是DEFINE_UUID應(yīng)該在實現(xiàn)文件(*.cpp,*.cxx,……)當(dāng)中使用。到這里,仍有一些使用方法與VC的關(guān)鍵字是不一樣的,所以仍沒做到仿真度100%。不過我相信通過預(yù)處理元編程,能夠相當(dāng)程度地逼近它,只是我對預(yù)處理元編程不是很了解,所以就不在這里獻(xiàn)丑了。