posts - 297,  comments - 15,  trackbacks - 0
出处 http://www.cppblog.com/cexer/archive/2008/07/06/55484.html

  VC当中有一个鲜为人知的关键字,除了微软自己的代码,我从未在任何地方看到有人用过它。虽然它的功能很强大,不过除非设计上的问题或是一些无法排除的困难,否则几乎从不会需要用到它的功能。但是有时候,它确实能作为一个最简单的解决方案而让某些设计过程事半功倍。

  借用 CCTV10《走近科学》的语气:那么这个神秘的关键关键字到底是什么呢?它又实现了什么神奇的功能呢?带着这一连串的疑问,让我们先来看一个具体的例子。

  我在自己曾经写的一个GUI框架当中,为了实现消息与处理函数自动映射的,就需要求助于这种功能。比如说有一个窗口类,它包含若干消息处理函数和一个消息与处理函数的映射 map:(请无视当中的 show() 和 create() 函数,与主题无关)

    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  进行循环检测,检测 Window 类是否存在该消息对应的处理函数。如果消息对应的处理函数存在,那么就将消息与函数的映射放进 m_handlerMap 当中。比如说消息 WM_CREATE,我检测类 Window是否存在 LRESULT onEvent( WindowEvent<WM_CREATE> ) 成员函数,在上例代码中是存在的,于是我将这样一个映射放进m_handlerMap:(真正实现的时候,还要考虑函数的类型。不同类型的函数,是不能直 接装进 map 当中的。不过在这里请无视例子当中涉及的所有类型转换,与主题无关)

    pair<WM_CREATE,&Window::onEvent>


  这样就达到了消息自动映射的目的。而不用像MFC一样手写宏去映射。(最后通过努力的确达到了我的目的,我的GUI框架能够进行自动消 息映射了,然而可以预见,由于几千个(0-WM_USER)循环,编译期的速度受到极大影响。所以最终我还是抛弃了这种自动映射实现,而采用了更高效神奇 的方法,这是后话也与本主题无关就先不提)。

  要实现以上的自动映射功能就引出了这样一个难题:如何编译期检测类的某特定名字的成员是否存在。

  功能不负有心人,经过爬山涉水翻山越岭,我终于在 MSDN 一个偏远角落里找着了传说当中那个神秘的关键字:__if_exists(其实还有一个 __if_not_exists)。MSDN 当中这样说明:__if_exists (__if_not_exists)允许你针对某符号的存在与否条件性地执行语句。使用语法:(注意检测的是“存在性”,而不是值)

    __if_exists ( /*你要检测存在性的函数或变量的名字*/ ) { 
 //做些有用的事
}


  MSDN当中的示例代码如下:
    // 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;
}


  以上代码的输出如下:(未测试,此输出为MSDN的说明文档当中的)

    In X<T>::Dump()
In A::Dump()
In X<T>::Dump()
T::Dump does not exist
g_bFlag = 1
C::f exists


  大概很少人见过这个关键字吧。虽然它们的功能与我的需求是如此的接近,但是面对如此强憾的关键字,我还是只能摇头叹息。我伤心地在文档 里看到说明,__if_exists(__if_not_exists)关键字用于函数的时候,只能根据函数名字进行检测,而会忽略对参数列表的检测,因 此没有对重载函数的分辨能力,而正是我需要的。比如类 Window 有一个函数:

    LRESULT Window::onEvent( WindowEvent<WM_DESTROY> )
{
//做些有用的事
}


  我用以下代码来检测 WM_CREATE 消息是否存在处理函数:

    __if_exists(Window::onEvent)
  {
      //添加消息映射
   }


  即使 Window 类当中不存在 LRESULT onEvent ( WindowEvent<WM_CREATE> ),以上测试也能通过。这是因为 __if_exists 关键字是不管函数重载的,如果存在一个 onEvent ,那么所有的检测都能通过。这不是我想要的。我需要比 __if_exists 更强憾的检测功能,强憾到能够针对不同参数列表的同名函数(重载函数)做出正确的存在性测试。

  于是我继续翻山越岭地寻找,从 CSDN 到 MSDN,从 SourceForge 到 CodeProject。要相信那句老话:“有心人天不负”。最后我在 CodeProject 上面看到一篇让我醍醐灌顶的文章:

  Interface Detection by Alexandre Courpron

  这篇文章从原理到实现,很详细地说明地一种编译期检测技术,先说明一下,由于VC7.1数千个bug当中的一个,以下技术不能在VC++7.1或更低版本上使用。具体的实现在那篇文章当中说得很详尽了,还是在这儿赘述一下。

  Alexandre Courpron的实现方式基于C++的这样一个规则:Substitution Failure Is Not An Error (简称SFINAE)。它的含义我也理解得比较含糊,不过它作用于重载函数的时候,可以这样理解:对于一个函数调用,在匹配函数的过程当中,如果最终能够 有一个函数匹配成功,那么对其余函数的匹配如果失败,编译器也不会视为错误。听起来有些麻烦,看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

  
  对于 call#1 编译器直接匹配 definition#1 成功。对于 call#2,编译器先用 definition#1 匹配 如下:

    void f( typename int::Type ) {}


  这显然是不正确的。不过编译器并没有编译失败报告错误,因为下面的 definition#2 匹配成功,根据 SFINAE的 规则,编译器有权保持沉默 。

  虽然是个小小的规则,在平时几乎不会注意它。然而在这儿,我们却可以利用它实现编译期检测的强大功能了,一个最简单的示例:

    #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;
}


  上面的代码会输出 1。说明一下检测的过程:

  1. 编译器遇到 isExists<TestClass>(0) 这一句,会去匹配 isExists 的两个重载函数。不定长的参数优先级更低,因此先匹配第一个函数。
  2. 第一个函数参数类型为 Param<&T::testFun>*,在这里是 Param<&TestClass::testFun>,编译器在匹配这个参数类型的时候会尝试实例化模板类 Param。
  3. 编 译器尝试用 &TestClass::testFun 去实例化 Param,因为 TestClass 确实存在一个 void (TestClass::*)() 类型,且名为 testFun 的成员函数。所以 Param 的实例化成功,因此参数匹配成功。
  4. 匹配第一个函数成功。编译器决定 isExists<TestClass>(0) 这一句调用就是调用的第一个函数。
  5. 因为第一个函数返回的类型为 Exists,用 sizeof 取大小就是 1。

  如果是我们把 TestClass 的定义修改为:(仅把函数的参数类型改为 int )

    struct TestClass
{
void testFun(int);
};


  这一次代码会输出 2。因为在第3步的时候,由于 TestClass 没有类型为 void (TestClass::*)(),且名为 testFun 的函数,所以实例化 Param 会失败,因此匹配第一个函数失败。然后编译器去匹配第二个函数。因为其参数类型是任意的,自然会匹配成功。结果会输出 2。

  当然这只是个最简单的示例,通过模板包装类。可以实现更灵活更强大的功能。比如回到那个自动消息映射的例子,用以下代码就能够实现了:

//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;
}




  以上代码会输出:

    1
0
0
1


  以上的示例代码再加上模板元编程,可以很轻易地实现消息的自动映射,具体实现这个已不在本贴的讨论范围并且这种自动映射的实现,太过复杂,在编译期没有效率,且不够灵活。不过在消息映射机制上来说,已称得上是一种革命性的尝试。

  在说完了这所有一切之后,再告诉你一个我最近才知道的秘密(不准笑我孤陋寡闻):其实 boost 库当中已有相关功能的 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

posted on 2008-07-19 13:50 chatler 阅读(445) 评论(0)  编辑 收藏 引用 所属分类: VC_MFC

只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   博问   Chat2DB   管理


<2009年7月>
2829301234
567891011
12131415161718
19202122232425
2627282930311
2345678

常用链接

留言簿(10)

随笔分类(307)

随笔档案(297)

algorithm

Books_Free_Online

C++

database

Linux

Linux shell

linux socket

misce

  • cloudward
  • 感觉这个博客还是不错,虽然做的东西和我不大相关,觉得看看还是有好处的

network

OSS

  • Google Android
  • Android is a software stack for mobile devices that includes an operating system, middleware and key applications. This early look at the Android SDK provides the tools and APIs necessary to begin developing applications on the Android platform using the Java programming language.
  • os161 file list

overall

搜索

  •  

最新评论

阅读排行榜

评论排行榜