转帖请注明出处 http://www.cppblog.com/cexer/archive/2008/07/05/55419.html
世界上有个叫__uuidof的关键字。这是一个家喻户晓且其被广泛使用的关键字,几乎可以说,有COM程序员的地方,就有它 __uuidof的存在。其很好很强大的程度是人所共见的,夸张一点比喻:离开它的COM程序员,就像失去了点火器的火箭,虽然可以人工点火,但是不安全且无效率。
不过很多人并不知道,这其实是一个编译器扩展关键字,提供了此关键字的仅VC一家别无它店。幸运的是,强大的C++让我们能够轻易仿真出这个关键字的大部分功能。
网上能够找到一种仿真的方法,见许式伟:《仿真VC++提供的关键字__uuidof》。该方法的实现是:特化模板类的成员函数,然后运行时调用函数根据UUID字符串产生出UUID,由于是生成于运行时,所以它无可避免地有两个缺点:
- 存在运行时消耗。
- 无法作为非类型模板参数传递给模板。
那些整天流着口水追求效率的C++程序员们,是不能忍受任何不必要的运行时消耗的。对于第二点,VC的关键字__uuidof取出来的UUID是能够作为非类型模板参数传递的,ATL中就大量地使用了这样的参数传递形式,所以目前的这种实现功能有限,仿真度还不够高。
其实只要能让它能够编译期决定UUID的值,那么这两个问题就迎刃而解了。而这是肯定可以实现的,并且很简单。我曾经在自己写的一个COM库里实现过这样的方法,虽然那个库已经不知丢到哪里去了,不过那个方法还记得。
解决的途径还是离不开模板特化。类的成员包括成员函数和成员变量,函数是运行时作用的,然而static const的成员变量可以是编译期就决定。所以解决的方法就在眼前了:特化模板的成员变量。
以下是我的实现方法。
先定义一个类模板,它有一个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;
作为非类型模板参数传递:
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;
不过现在这种实现还有一些问题,看以下代码:
IID ITypelibID=_uuid_of_impl<ITypeLib>::id;
注意我们并没有事先对模板__uuid_of_impl特化ITypeLib的版本。但是以上语句却能够编译通过,在运行时,__uuid_of_impl<ITypeLib>的值将会是错误的值GUID_NULL。这是因为,我们定义模板的时候,同时在模板外定义了模板的静态成员变量并赋值为GUID_NULL,所以没有用特化的方法定义UUID的接口,都将使用GUID_NULL这个通用值。这当然不是我们想要的。所以我们想在没有定义UUID的时候让编译器警告我们,要达到这样的效果只需要去掉上面那句:
template<typename T>
const UUID _uuid_of_impl<T>::id=GUID_NULL;
现在再进行编译,编译器会告诉你,有一个无法解析的符号。根据编译器提供的相关信息,很容易就能确定问题所在。这样能够在编译期极大地减小安全隐患。
最后加上我们定义的几个宏,这是最后的全部实现:
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应该在实现文件(*.cpp,*.cxx,……)当中使用。到这里,仍有一些使用方法与VC的关键字是不一样的,所以仍没做到仿真度100%。不过我相信通过预处理元编程,能够相当程度地逼近它,只是我对预处理元编程不是很了解,所以就不在这里献丑了。