转帖请注明出处 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 ( /*你要检测存在性的函数或变量的名字*/ ) { //做些有用的事 }
// 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。说明一下检测的过程:
- 编译器遇到 isExists<TestClass>(0) 这一句,会去匹配 isExists 的两个重载函数。不定长的参数优先级更低,因此先匹配第一个函数。
- 第一个函数参数类型为 Param<&T::testFun>*,在这里是 Param<&TestClass::testFun>,编译器在匹配这个参数类型的时候会尝试实例化模板类 Param。
- 编 译器尝试用 &TestClass::testFun 去实例化 Param,因为 TestClass 确实存在一个 void (TestClass::*)() 类型,且名为 testFun 的成员函数。所以 Param 的实例化成功,因此参数匹配成功。
- 匹配第一个函数成功。编译器决定 isExists<TestClass>(0) 这一句调用就是调用的第一个函数。
- 因为第一个函数返回的类型为 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。
![](https://i-blog.csdnimg.cn/blog_migrate/16a011c0ead9fc6826ed080253c6fc6f.jpeg)