轉(zhuǎn)帖請(qǐng)注明出處 http://m.shnenglu.com/cexer/archive/2008/07/06/55484.html
VC當(dāng)中有一個(gè)鮮為人知的關(guān)鍵字,除了微軟自己的代碼,我從未在任何地方看到有人用過(guò)它。雖然它的功能很強(qiáng)大,不過(guò)除非設(shè)計(jì)上的問(wèn)題或是一些無(wú)法排除的困難,否則幾乎從不會(huì)需要用到它的功能。但是有時(shí)候,它確實(shí)能作為一個(gè)最簡(jiǎn)單的解決方案而讓某些設(shè)計(jì)過(guò)程事半功倍。
借用 CCTV10《走近科學(xué)》的語(yǔ)氣:那么這個(gè)神秘的關(guān)鍵關(guān)鍵字到底是什么呢?它又實(shí)現(xiàn)了什么神奇的功能呢?帶著這一連串的疑問(wèn),讓我們先來(lái)看一個(gè)具體的例子。
我在自己曾經(jīng)寫(xiě)的一個(gè)GUI框架當(dāng)中,為了實(shí)現(xiàn)消息與處理函數(shù)自動(dòng)映射的,就需要求助于這種功能。比如說(shuō)有一個(gè)窗口類(lèi),它包含若干消息處理函數(shù)和一個(gè)消息與處理函數(shù)的映射 map:(請(qǐng)無(wú)視當(dāng)中的 show() 和 create() 函數(shù),與主題無(wú)關(guān))
class Window
{
typedef UINT _Message;
typedef LRESULT (Window::*_Handler)(_Message);
map<_Message,_Handler> m_handlerMap;
public:
bool show();
bool create();
public:
LRESULT onEvent( WindowEvent<WM_CREATE> );
LRESULT onEvent( WindowEvent<WM_DESTROY> );
};
我需要利用模板元編程 從 0 到 WM_USER 進(jìn)行循環(huán)檢測(cè),檢測(cè) Window 類(lèi)是否存在該消息對(duì)應(yīng)的處理函數(shù)。如果消息對(duì)應(yīng)的處理函數(shù)存在,那么就將消息與函數(shù)的映射放進(jìn) m_handlerMap 當(dāng)中。比如說(shuō)消息 WM_CREATE,我檢測(cè)類(lèi) Window是否存在 LRESULT onEvent( WindowEvent<WM_CREATE> ) 成員函數(shù),在上例代碼中是存在的,于是我將這樣一個(gè)映射放進(jìn)m_handlerMap:(真正實(shí)現(xiàn)的時(shí)候,還要考慮函數(shù)的類(lèi)型。不同類(lèi)型的函數(shù),是不能直接裝進(jìn) map 當(dāng)中的。不過(guò)在這里請(qǐng)無(wú)視例子當(dāng)中涉及的所有類(lèi)型轉(zhuǎn)換,與主題無(wú)關(guān))
pair<WM_CREATE,&Window::onEvent>
這樣就達(dá)到了消息自動(dòng)映射的目的。而不用像MFC一樣手寫(xiě)宏去映射。(最后通過(guò)努力的確達(dá)到了我的目的,我的GUI框架能夠進(jìn)行自動(dòng)消息映射了,然而可以預(yù)見(jiàn),由于幾千個(gè)(0-WM_USER)循環(huán),編譯期的速度受到極大影響。所以最終我還是拋棄了這種自動(dòng)映射實(shí)現(xiàn),而采用了更高效神奇的方法,這是后話也與本主題無(wú)關(guān)就先不提)。
要實(shí)現(xiàn)以上的自動(dòng)映射功能就引出了這樣一個(gè)難題:如何編譯期檢測(cè)類(lèi)的某特定名字的成員是否存在。
功能不負(fù)有心人,經(jīng)過(guò)爬山涉水翻山越嶺,我終于在 MSDN 一個(gè)偏遠(yuǎn)角落里找著了傳說(shuō)當(dāng)中那個(gè)神秘的關(guān)鍵字:__if_exists(其實(shí)還有一個(gè) __if_not_exists)。MSDN 當(dāng)中這樣說(shuō)明:__if_exists (__if_not_exists)允許你針對(duì)某符號(hào)的存在與否條件性地執(zhí)行語(yǔ)句。使用語(yǔ)法:(注意檢測(cè)的是“存在性”,而不是值)
__if_exists ( /*你要檢測(cè)存在性的函數(shù)或變量的名字*/ ) {
//做些有用的事
}
MSDN當(dāng)中的示例代碼如下:
// the__if_exists_statement.cpp
// compile with: /EHsc
#include <iostream>
template<typename T>
class X : public T {
public:
void Dump() {
std::cout << "In X<T>::Dump()" << std::endl;
__if_exists(T::Dump) {
T::Dump();
}
__if_not_exists(T::Dump) {
std::cout << "T::Dump does not exist" << std::endl;
}
}
};
class A {
public:
void Dump() {
std::cout << "In A::Dump()" << std::endl;
}
};
class B {};
bool g_bFlag = true;
class C {
public:
void f(int);
void f(double);
};
int main() {
X<A> x1;
X<B> x2;
x1.Dump();
x2.Dump();
__if_exists(::g_bFlag) {
std::cout << "g_bFlag = " << g_bFlag << std::endl;
}
__if_exists(C::f) {
std::cout << "C::f exists" << std::endl;
}
return 0;
}
以上代碼的輸出如下:(未測(cè)試,此輸出為MSDN的說(shuō)明文檔當(dāng)中的)
In X<T>::Dump()
In A::Dump()
In X<T>::Dump()
T::Dump does not exist
g_bFlag = 1
C::f exists
大概很少人見(jiàn)過(guò)這個(gè)關(guān)鍵字吧。雖然它們的功能與我的需求是如此的接近,但是面對(duì)如此強(qiáng)憾的關(guān)鍵字,我還是只能搖頭嘆息。我傷心地在文檔里看到說(shuō)明,__if_exists(__if_not_exists)關(guān)鍵字用于函數(shù)的時(shí)候,只能根據(jù)函數(shù)名字進(jìn)行檢測(cè),而會(huì)忽略對(duì)參數(shù)列表的檢測(cè),因此沒(méi)有對(duì)重載函數(shù)的分辨能力,而正是我需要的。比如類(lèi) Window 有一個(gè)函數(shù):
LRESULT Window::onEvent( WindowEvent<WM_DESTROY> )
{
//做些有用的事
}
我用以下代碼來(lái)檢測(cè) WM_CREATE 消息是否存在處理函數(shù):
__if_exists(Window::onEvent)
{
//添加消息映射
}
即使 Window 類(lèi)當(dāng)中不存在 LRESULT onEvent ( WindowEvent<WM_CREATE> ),以上測(cè)試也能通過(guò)。這是因?yàn)?__if_exists 關(guān)鍵字是不管函數(shù)重載的,如果存在一個(gè) onEvent ,那么所有的檢測(cè)都能通過(guò)。這不是我想要的。我需要比 __if_exists 更強(qiáng)憾的檢測(cè)功能,強(qiáng)憾到能夠針對(duì)不同參數(shù)列表的同名函數(shù)(重載函數(shù))做出正確的存在性測(cè)試。
于是我繼續(xù)翻山越嶺地尋找,從 CSDN 到 MSDN,從 SourceForge 到 CodeProject。要相信那句老話:“有心人天不負(fù)”。最后我在 CodeProject 上面看到一篇讓我醍醐灌頂?shù)奈恼拢?/p>
Interface Detection by Alexandre Courpron
這篇文章從原理到實(shí)現(xiàn),很詳細(xì)地說(shuō)明地一種編譯期檢測(cè)技術(shù),先說(shuō)明一下,由于VC7.1數(shù)千個(gè)bug當(dāng)中的一個(gè),以下技術(shù)不能在VC++7.1或更低版本上使用。具體的實(shí)現(xiàn)在那篇文章當(dāng)中說(shuō)得很詳盡了,還是在這兒贅述一下。
Alexandre Courpron的實(shí)現(xiàn)方式基于C++的這樣一個(gè)規(guī)則:Substitution Failure Is Not An Error(簡(jiǎn)稱(chēng)SFINAE)。它的含義我也理解得比較含糊,不過(guò)它作用于重載函數(shù)的時(shí)候,可以這樣理解:對(duì)于一個(gè)函數(shù)調(diào)用,在匹配函數(shù)的過(guò)程當(dāng)中,如果最終能夠有一個(gè)函數(shù)匹配成功,那么對(duì)其余函數(shù)的匹配如果失敗,編譯器也不會(huì)視為錯(cuò)誤。聽(tīng)起來(lái)有些麻煩,看Alexandre Courpron給出的例子:
struct Test
{
typedef int Type;
};
template < typename T >
void f(typename T::Type) {} // definition #1
template<typename T>
void f(T){} // definition #2
f<Test>(10); //call #1
f<int>(10); //call #2
對(duì)于 call#1 編譯器直接匹配 definition#1 成功。對(duì)于 call#2,編譯器先用 definition#1 匹配 如下:
void f( typename int::Type ) {}
這顯然是不正確的。不過(guò)編譯器并沒(méi)有編譯失敗報(bào)告錯(cuò)誤,因?yàn)橄旅娴?definition#2 匹配成功,根據(jù) SFINAE的 規(guī)則,編譯器有權(quán)保持沉默 。
雖然是個(gè)小小的規(guī)則,在平時(shí)幾乎不會(huì)注意它。然而在這兒,我們卻可以利用它實(shí)現(xiàn)編譯期檢測(cè)的強(qiáng)大功能了,一個(gè)最簡(jiǎn)單的示例:
#include <iostream>
using namespace std;
//
struct TestClass
{
void testFun();
};
struct Exists { char x;};
struct NotExists { char x[2]; };
template <void (TestClass::*)()>
struct Param ;
template <class T>
Exists isExists( Param<&T::testFun>* );
template <class T>
NotExists isExists( ... );
//
int main()
{
cout<<sizeof(isExists<TestClass>(0))<<endl;
}
上面的代碼會(huì)輸出 1。說(shuō)明一下檢測(cè)的過(guò)程:
- 編譯器遇到 isExists<TestClass>(0) 這一句,會(huì)去匹配 isExists 的兩個(gè)重載函數(shù)。不定長(zhǎng)的參數(shù)優(yōu)先級(jí)更低,因此先匹配第一個(gè)函數(shù)。
- 第一個(gè)函數(shù)參數(shù)類(lèi)型為 Param<&T::testFun>*,在這里是 Param<&TestClass::testFun>,編譯器在匹配這個(gè)參數(shù)類(lèi)型的時(shí)候會(huì)嘗試實(shí)例化模板類(lèi) Param。
- 編譯器嘗試用 &TestClass::testFun 去實(shí)例化 Param,因?yàn)?TestClass 確實(shí)存在一個(gè) void (TestClass::*)() 類(lèi)型,且名為 testFun 的成員函數(shù)。所以 Param 的實(shí)例化成功,因此參數(shù)匹配成功。
- 匹配第一個(gè)函數(shù)成功。編譯器決定 isExists<TestClass>(0) 這一句調(diào)用就是調(diào)用的第一個(gè)函數(shù)。
- 因?yàn)榈谝粋€(gè)函數(shù)返回的類(lèi)型為 Exists,用 sizeof 取大小就是 1。
如果是我們把 TestClass 的定義修改為:(僅把函數(shù)的參數(shù)類(lèi)型改為 int )
struct TestClass
{
void testFun(int);
};
這一次代碼會(huì)輸出 2。因?yàn)樵诘冢巢降臅r(shí)候,由于 TestClass 沒(méi)有類(lèi)型為 void (TestClass::*)(),且名為 testFun 的函數(shù),所以實(shí)例化 Param 會(huì)失敗,因此匹配第一個(gè)函數(shù)失敗。然后編譯器去匹配第二個(gè)函數(shù)。因?yàn)槠鋮?shù)類(lèi)型是任意的,自然會(huì)匹配成功。結(jié)果會(huì)輸出 2。
當(dāng)然這只是個(gè)最簡(jiǎn)單的示例,通過(guò)模板包裝類(lèi)。可以實(shí)現(xiàn)更靈活更強(qiáng)大的功能。比如回到那個(gè)自動(dòng)消息映射的例子,用以下代碼就能夠?qū)崿F(xiàn)了:
//c++std
#include <iostream>
using namespace std;
//windows
#include <windows.h>
//detector
template<typename TWindow,UINT t_msg>
struct MessageHandlerDetector
{
typedef WindowEvent<t_msg> _Event;
struct Exists {char x;};
struct NotExists {char x[2];};
template<LRESULT (TWindow::*)(_Event)>
struct Param;
template<typename T>
static Exists detect( Param<&T::onEvent>* );
template<typename T>
static NotExists detect( ... );
public:
enum{isExists=sizeof(detect<TWindow>(0))==sizeof(Exists)};
};
//test classes
struct Window
{
LRESULT onEvent( WindowEvent<WM_CREATE> );
};
struct Button
{
LRESULT onEvent( WindowEvent<WM_DESTROY> );
};
//main
int main()
{
cout<<MessageHandlerDetector<Window,WM_CREATE>::isExists<<endl;
cout<<MessageHandlerDetector<Window,WM_DESTROY>::isExists<<endl;
cout<<MessageHandlerDetector<Button,WM_CREATE>::isExists<<endl;
cout<<MessageHandlerDetector<Button,WM_DESTROY>::isExists<<endl;
return 0;
}
以上代碼會(huì)輸出:
以上的示例代碼再加上模板元編程,可以很輕易地實(shí)現(xiàn)消息的自動(dòng)映射,具體實(shí)現(xiàn)這個(gè)已不在本貼的討論范圍并且這種自動(dòng)映射的實(shí)現(xiàn),太過(guò)復(fù)雜,在編譯期沒(méi)有效率,且不夠靈活。不過(guò)在消息映射機(jī)制上來(lái)說(shuō),已稱(chēng)得上是一種革命性的嘗試。
在說(shuō)完了這所有一切之后,再告訴你一個(gè)我最近才知道的秘密(不準(zhǔn)笑我孤陋寡聞):其實(shí) boost 庫(kù)當(dāng)中已有相關(guān)功能的 MPL 工具存在,叫做 has_xxx。
源文件:<boost\mpl\has_xxx.hpp>
文檔:http://www.boost.org/doc/libs/1_35_0/libs/mpl/doc/refmanual/has-xxx-trait-def.html。