class __declspec(uuid("B372C9F6-1959-4650-960D-73F20CD479BA")) Class;
struct __declspec(uuid("B372C9F6-1959-4650-960D-73F20CD479BB")) Interface;
void test()
{
CLSID clsid=__uuidof(Class);
IID iid=__uuidof(Interface);
...
}
q比起你以前定义uuid的方法简单多了吧Q可惜,q样好用的东西,它只在VC++中提供。不q没有关p,我们q里介绍一个技巧,可以
让你在几乎所有C++~译器中都可以这h便的使用__uuidof。这里没有说是所有,是因为我们用了模板特化技术,可能存在一些比?#8220;古?#8221;?
C++~译器,不支持该Ҏ?
也许你已l迫不及待了。好Q让我们来看看:
#include <string>
#include <cassert>
inline STDMETHODIMP_(GUID) GUIDFromString(LPOLESTR lpsz)
{
HRESULT hr;
GUID guid;
if (lpsz[0]=='{')
{
hr=CLSIDFromString(lpsz,&guid);
}
else
{
std::basic_string<OLECHAR> strGuid;
strGuid.append(1,'{');
strGuid.append(lpsz);
strGuid.append(1,'}');
hr = CLSIDFromString((LPOLESTR)strGuid.c_str(),&guid);
}
assert(hr==S_OK);
return guid;
}
template <class Class>
struct _UuidTraits {
};
#define _DEFINE_UUID(Class,uuid) \
template <> \
struct _UuidTraits<Class>{ \
static const GUID& Guid(){ \
static GUID guid=GUIDFromString(L## uuid); \
return guid; \
} \
}
#define __uuidof(Class) _UuidTraits<Class>::Guid()
#define DEFINE_CLSID(Class,guid) \
class Class; \
_DEFINE_UUID(Class,guid)
#define DEFINE_IID(Interface,iid) \
struct Interface; \
_DEFINE_UUID(Interface,iid)
q样一来,已l模拟出一个__uuidof关键字。我们可以很方便q行uuid的定义。D例如下:
void test()
{
CLSID clsid=__uuidof(Class);
IID iid=__uuidof(Interface);
...
}
在VC++中,Z与其他编译器以相同的方式来进行uuid的定义,我们不直接用__declspec(uuid)Q而是也定义DEFINE_CLSID, DEFINE_IID宏:
#define DEFINE_CLSID(Class,clsid) \
class __declspec(uuid(clsid)) Class
#define DEFINE_IID(Interface,iid) \
struct __declspec(uuid(iid)) Interface
q样一来,我们已经在所有包含VC++在内的支持模板特化技术的~译器中Q提供了__uuidof关键字。通过它可以进一步简化你在C++语言中实现COMlg的代仗?
附注Q关于本文用的C++模板的特化技术,详细请参阅C++文法斚w的书c,例如《C++ Primer》。其实这个技巧在C++标准库——STL中有一个专门的名字QtraitsQ萃取)Q你可以在很多介lSTL的书c中见到相关的介l?
转注Q因׃后写的文章需要用到类似的技巧,因此先搬q来放这?/p>
不过很多人ƈ不知道,q其实是一个编译器扩展关键字,提供了此关键字的仅VC一家别无它店。幸q的是,强大的C++让我们能够轻易仿真出q个关键字的大部分功能?
|上能够扑ֈ一U仿真的ҎQ见许式?《仿真VC++提供的关键字__uuidof?/a>。该Ҏ的实现是Q特化模板类的成员函敎ͼ然后q行时调用函数根据UUID字符串生出UUIDQ由于是生成于运行时Q所以它无可避免地有两个~点Q?
那些整天着口水q求效率的C++E序员们Q是不能忍受M不必要的q行时消耗的。对于第二点QVC的关键字__uuidof取出来的UUID 是能够作为非cd模板参数传递的QATL中就大量C用了q样的参C递Ş式,所以目前的q种实现功能有限Q仿真度q不够高?
其实只要能让它能够编译期军_UUID的|那么q两个问题就q刃而解了。而这是肯定可以实现的Qƈ且很单。我曄在自己写的一个COM库里实现q这LҎQ虽焉个库已经不知丢到哪里MQ不q那个方法还记得?
解决的途径q是M开模板特化。类的成员包括成员函数和成员变量Q函数是q行时作用的Q然而static const的成员变量可以是~译期就军_。所以解决的Ҏ在眼前了:特化模板的成员变量?
以下是我的实现方法?
先定义一个类模板Q它有一个static const ,UUIDcd的成员变量:
template<typename T>
struct _uuid_of_impl
{
static const UUID id;
};
template<typename T>
const UUID _uuid_of_impl<T>::id=GUID_NULL;
有了q个单的东西好办了Q只需要针Ҏ个接口特它的成员变量p了,如:
template<>
const UUID _uuid_of_impl<IUnknown>::id=IID_IUnknown;
template<>
const UUID _uuid_of_impl<IDispatch>::id=IID_IDispatch;
然后我们可以这样取得接口的UUIDQ?/p>
IID IunknownID=_uuid_of_impl<IUnknown>::id;
IID IdispatchID=_uuid_of_impl<IDispatch>::id;
作ؓ非类型模板参C递:
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;
不过现在q种实现q有一些问题,看以下代码:
IID ITypelibID=_uuid_of_impl<ITypeLib>::id;
注意我们q没有事先对模板__uuid_of_impl特化ITypeLib的版本。但是以上语句却能够~译通过Q在q行Ӟ
__uuid_of_impl<ITypeLib>的值将会是错误的值GUID_NULL。这是因为,我们定义模板的时候,同时在模板外定义
了模板的静态成员变量ƈ赋gؓGUID_NULLQ所以没有用特化的方法定义UUID的接口,都将使用GUID_NULLq个通用倹{这当然不是我们惌
的。所以我们想在没有定义UUID的时候让~译器警告我们,要达到这L效果只需要去掉上面那句:
template<typename T>
const UUID _uuid_of_impl<T>::id=GUID_NULL;
现在再进行编译,~译器会告诉你,有一个无法解析的W号。根据编译器提供的相关信息,很容易就能确定问题所在。这栯够在~译期极大地减小安全隐患?
最后加上我们定义的几个宏,q是最后的全部实现Q?/p>
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}}
用以下代码测试通过Q?/p>
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应该在实现文Ӟ*.cppQ?.cxxQ?#8230;…Q当中用。到q里Q仍有一些用方法与VC的关?
字是不一LQ所以仍没做C真度100%。不q我怿通过预处理元~程Q能够相当程度地D它,只是我对预处理元~程不是很了解,所以就不在q里献丑
了?/p>
借用 CCTV10《走q科学》的语气Q那么这个神U的关键关键字到底是什么呢Q它又实C什么神奇的功能呢?带着q一q串的疑问,让我们先来看一个具体的例子?/p>
我在自己曄写的一个GUI框架当中Qؓ了实现消息与处理函数自动映射的,需要求助于q种功能。比如说有一个窗口类Q它包含若干消息处理函数和一个消息与处理函数的映?mapQ(h视当中的 show() ?create() 函数Q与主题无关Q?/p>
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 q行循环,?Window
cL否存在该消息对应的处理函数。如果消息对应的处理函数存在Q那么就消息与函数的映放q?m_handlerMap 当中。比如说消息
WM_CREATEQ我类 Window是否存在 LRESULT onEvent( WindowEvent<WM_CREATE>
)
成员函数Q在上例代码中是存在的,于是我将q样一个映放qm_handlerMapQ(真正实现的时候,q要考虑函数的类型。不同类型的函数Q是不能?
接装q?map 当中的。不q在q里h视例子当中涉及的所有类型{换,与主题无养I
pair<WM_CREATE,&Window::onEvent>
q样pC消息自动映射的目的。而不用像MFC一h写宏L。(最后通过努力的确辑ֈ了我的目的,我的GUI框架能够q行自动?
息映了Q然而可以预见,׃几千个(0QWM_USERQ@环,~译期的速度受到极大影响。所以最l我q是抛弃了这U自动映实玎ͼ而采用了更高效神?
的方法,q是后话也与本主题无兛_先不提)?/p>
要实C上的自动映射功能引Zq样一个难题:如何~译期检类的某特定名字的成员是否存在?/p>
功能不负有心人,l过爬山涉水d岭Q我l于?MSDN
一个偏q角落里扄了传说当中那个神U的关键字:__if_existsQ其实还有一?__if_not_existsQ。MSDN
当中q样说明Q__if_exists
Q__if_not_existsQ允怽针对某符L存在与否条g性地执行语句。用语法:Q注意检的?#8220;存在?#8221;Q而不是|
__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;
}
以上代码的输出如下:Q未试Q此输出为MSDN的说明文当中的Q?/p>
In X<T>::Dump()
In A::Dump()
In X<T>::Dump()
T::Dump does not exist
g_bFlag = 1
C::f exists
大概很少q这个关键字吧。虽然它们的功能与我的需求是如此的接q,但是面对如此强憾的关键字Q我q是只能摇头Ҏ。我伤心地在文
里看到说明,__if_existsQ__if_not_existsQ关键字用于函数的时候,只能Ҏ函数名字q行,而会忽略对参数列表的,?
此没有对重蝲函数的分辨能力,而正是我需要的。比如类 Window 有一个函敎ͼ
LRESULT Window::onEvent( WindowEvent<WM_DESTROY> )
{
//做些有用的事
}
我用以下代码来检?WM_CREATE 消息是否存在处理函数Q?/p>
__if_exists(Window::onEvent)
{
//d消息映射
}
即 Window cd中不存在 LRESULT onEvent (
WindowEvent<WM_CREATE> )Q以上测试也能通过。这是因?__if_exists
关键字是不管函数重蝲的,如果存在一?onEvent Q那么所有的都能通过。这不是我想要的。我需要比 __if_exists
更强憄功能,强憾到能够针对不同参数列表的同名函数Q重载函敎ͼ做出正确的存在性测试?/p>
于是我l翻p岭地LQ从 CSDN ?MSDNQ从 SourceForge ?CodeProject。要怿那句老话Q?#8220;有心人天不负”。最后我?CodeProject 上面看到一让我醍醐灌的文章Q?/p>
Interface Detection by Alexandre Courpron
q篇文章从原理到实现Q很详细地说明地一U编译期技术,先说明一下,׃VC7.1数千个bug当中的一个,以下技术不能在VC++7.1或更低版本上使用。具体的实现在那文章当中说得很详尽了,q是在这儿赘qC下?/p>
Alexandre Courpron的实现方式基于C++的这样一个规则:Substitution Failure Is Not An Error Q简USFINAEQ。它的含义我也理解得比较含糊Q不q它作用于重载函数的时候,可以q样理解Q对于一个函数调用,在匹配函数的q程当中Q如果最l能? 有一个函数匹配成功,那么对其余函数的匚w如果p|Q编译器也不会视为错误。听h有些ȝQ看Alexandre Courpronl出的例子:
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#2Q编译器先用 definition#1 匚w 如下Q?/p>
void f( typename int::Type ) {}
q显然是不正的。不q编译器q没有编译失败报告错误,因ؓ下面?definition#2 匚w成功Q根?SFINAE?规则Q编译器有权保持沉默 ?/p>
虽然是个小的规则,在^时几乎不会注意它。然而在q儿Q我们却可以利用它实现编译期的强大功能了,一个最单的CZQ?/p>
#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;
}
上面的代码会输出 Q。说明一下检的q程Q?/p>
如果是我们把 TestClass 的定义修改ؓQ(仅把函数的参数类型改?int Q?/p>
struct TestClass
{
void testFun(int);
};
q一ơ代码会输出 Q。因为在W3步的时候,׃ TestClass 没有cd?void
(TestClass::*)()Q且名ؓ testFun 的函敎ͼ所以实例化 Param
会失败,因此匚wW一个函数失败。然后编译器d配第二个函数。因为其参数cd是Q意的Q自然会匚w成功。结果会输出 2?/p>
当然q只是个最单的CZQ通过模板包装cR可以实现更灉|更强大的功能。比如回到那个自动消息映的例子Q用以下代码p够实CQ?/p>
//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
以上的示例代码再加上模板元编E,可以很轻易地实现消息的自动映,具体实现q个已不在本贴的讨论范围q且q种自动映射的实玎ͼ太过复杂Q在~译期没有效率,且不够灵zR不q在消息映射机制上来_已称得上是一U革命性的试?/p>
在说完了q所有一切之后,再告诉你一个我最q才知道的秘密(不准W我孤陋寡闻Q:其实 boost 库当中已有相兛_能的 MPL 工具存在Q叫?has_xxx?/p>
源文Ӟ<boost\mpl\has_xxx.hpp>
文档Q?a target="_blank">http://www.boost.org/doc/libs/1_35_0/libs/mpl/doc/refmanual/has-xxx-trait-def.html?/p>