Visual Studio系列中產(chǎn)品中,Visual Studio 6.0是最經(jīng)典的一個(gè)版本,雖然后來(lái)有Visual Studio .NET 2003,以及2005,也確實(shí)添加了很多讓我覺(jué)得激動(dòng)的特性,但是從使用細(xì)節(jié)的細(xì)膩程度上來(lái)看,VS 6.0無(wú)疑是最棒的。我們一些同事甚至試圖把2005的C++編譯器獨(dú)立的拿到Visual Studio 6.0中來(lái)用,也不愿意升級(jí)到.NET上來(lái)用,可見(jiàn)其魅力。
和VS 6.0這個(gè)產(chǎn)品的成熟相比,VC++ 6.0的編譯器的的確確相對(duì)來(lái)說(shuō)有些糟糕,其中最被詬病的是對(duì)模板技術(shù)支持很不好。下面我想做的一件事情,就是向那些繼續(xù)留戀VC++ 6.0的朋友,介紹一些小花招,來(lái)避開(kāi)VC++ 6.0的一些編譯器缺陷。
1)for (type var=expression;;) 中變量var的作用域問(wèn)題。
按照C++標(biāo)準(zhǔn),這里定義的變量var出了for循環(huán)應(yīng)該被銷毀。也就是說(shuō)下面這段代碼是有效的:
for (int i = 0; i < 100; ++i)
func();
for (int i = 0; i < 100; ++i)
func2();
而下面這段代碼應(yīng)該編譯不過(guò):
for (int i = 0; i < 100; ++i)
{
if (has_found_it())
{
handle_find_result();
break;
}
}
if (i == 100)
do_not_found();
然而VC++ 6.0對(duì)于第一段代碼會(huì)報(bào)變量i重復(fù)定義錯(cuò)誤,而第二段代碼編譯通過(guò)。
為了讓VC++ 6.0的for語(yǔ)句看起來(lái)符合C++標(biāo)準(zhǔn),你可以這樣做:
#define for if (0); else for
你會(huì)發(fā)現(xiàn)很有趣,這樣define一下后,VC++ 6.0的for語(yǔ)句完全符合C++標(biāo)準(zhǔn)了!而且由于編譯器的優(yōu)化,Release版本不會(huì)增加任何額外的開(kāi)銷。
喜歡“鉆牛角尖”的朋友可能會(huì)說(shuō):嗯,不錯(cuò)的主意。但是——為什么不這樣做:
#define for if (1) for
嗯?看起來(lái)也可以。還是讓我們看一個(gè)用例:
if (cond)
for (int i = 0; i < 100; ++i)
func1();
else
func2();
進(jìn)行宏代碼展開(kāi)后,成為:
if (cond)
if (1)
for (int i = 0; i < 100; ++i)
func1();
else
func2();
這個(gè)結(jié)果顯然不能符合我們的原意。這里func2();語(yǔ)句永遠(yuǎn)得不到執(zhí)行機(jī)會(huì)。
2)模板參數(shù)類型如果不出現(xiàn)在參數(shù)列表中,則不能作為返回值類型。
由于編譯器的缺陷,VC++ 6.0不支持以下這種用法:
template <class T1, class T2>
T1 func(T2 arg)
{
T1 var;
... // 處理var過(guò)程
return var;
}
void test()
{
int result1 = func<int>(1);
double result2 = func<double>(2);
};
很抱歉,這種用法VC++ 6.0不支持。讓人惱火的是,VC++ 6.0編譯時(shí)不會(huì)提示錯(cuò)誤,但是生成的執(zhí)行代碼卻很成問(wèn)題。
究其原因,是因?yàn)?span lang="EN-US">VC++ 6.0的template技術(shù)是在編譯器的較高層次做的,真正的編譯器核心并不考慮模板。以上面的代碼為例,對(duì)編譯器核心來(lái)說(shuō),只是有兩個(gè)重載函數(shù)而已:
int func(int arg);
double func(int arg);
如果是普通情況,只是返回值不同的函數(shù),是不能同時(shí)存在的,編譯器應(yīng)該認(rèn)為這是一個(gè)錯(cuò)誤。但是很在模板情況下,這兩個(gè)函數(shù)被簡(jiǎn)單認(rèn)為是同一個(gè)函數(shù)。因?yàn)?span lang="EN-US">VC++ 6.0會(huì)為每個(gè)函數(shù)根據(jù)它的:
1)所在的namespace;
2)所在的類的類名(如果是成員函數(shù));
3)函數(shù)名;
4)函數(shù)調(diào)用方式(cdecl、stdcall還是fastcall);
5)所有參數(shù)的類型;
而生成一個(gè)唯一標(biāo)識(shí)該函數(shù)的函數(shù)名。這個(gè)過(guò)程叫Name Mangling,是所有C++編譯器都要進(jìn)行的工作。而另一個(gè)背景是,很多C++編譯器生成的目標(biāo)文件(.obj文件)有一些和模板相關(guān)的特殊信息,包括也標(biāo)識(shí)了某個(gè)函數(shù)是否模板函數(shù)。這是因?yàn)橐粋€(gè)模板函數(shù)在多個(gè)源文件(.cpp文件)中被調(diào)用的話,這個(gè)模板函數(shù)就會(huì)在這些源文件編譯生成的目標(biāo)文件(.obj文件)中都定義(definition)一份。為了支持模板,link程序顯然必須知道這個(gè)函數(shù)是模板函數(shù),從而隨意選擇一個(gè)定義(丟棄其余的定義),而不是報(bào)符號(hào)重復(fù)定義錯(cuò)誤。
因?yàn)楹瘮?shù)名、參數(shù)列表等完全一致,所以這兩個(gè)函數(shù)Name Mangling后生成的名字是一樣的,并且,它們都被標(biāo)識(shí)為這是模板函數(shù)。從而,link程序在工作的時(shí)候,簡(jiǎn)單地將其中一個(gè)函數(shù)定義給拋棄了。
那么,如果我們非要提供上述的func函數(shù),怎么辦?我們來(lái)點(diǎn)花招:
template <class T1>
class func
{
private:
T1 var;
public:
template <class T2>
func(T2 arg)
{
... // 處理var過(guò)程
}
operator T1() const
{
return var;
}
};
我們?cè)賮?lái)使用func這個(gè)“函數(shù)”:
void test()
{
int result1 = func<int>(1);
double result2 = func<double>(2);
};
呵呵,你會(huì)發(fā)現(xiàn),它還真象是你期望的正常工作。
3)仿真VC++提供的關(guān)鍵字__uuidof。
這個(gè)技巧不是針對(duì)VC++ 6.0缺陷的,而是針對(duì)VC++擴(kuò)展語(yǔ)法的。這個(gè)技巧的來(lái)由,是為了某些希望有一天有可能要脫離Visual C++環(huán)境進(jìn)行開(kāi)發(fā)的人員。為了脫離VC++,你需要謹(jǐn)慎使用它的所有擴(kuò)展語(yǔ)法。例如本文討論的__uuidof。我們先來(lái)看看一個(gè)例子:
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);
...
}
這比起你以前定義uuid的方法簡(jiǎn)單多了吧?可惜,這樣好用的東西,它只在VC++中提供。不過(guò)沒(méi)有關(guān)系,我們這里介紹一個(gè)技巧,可以讓你在幾乎所有C++編譯器中都可以這樣方便的使用__uuidof。這里沒(méi)有說(shuō)是所有,是因?yàn)槲覀兪褂昧四0逄鼗夹g(shù),可能存在一些比較“古老”的C++編譯器,不支持該特性。
也許你已經(jīng)迫不及待了。好,讓我們來(lái)看看:
#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)
這樣一來(lái),就已經(jīng)模擬出一個(gè)__uuidof關(guān)鍵字。我們可以很方便進(jìn)行uuid的定義。舉例如下:
DEFINE_CLSID(Class, "{B372C9F6-1959-4650-960D-73F20CD479BA}");
DEFINE_IID(Interface, "{B372C9F6-1959-4650-960D-73F20CD479BB}");
void test()
{
CLSID clsid = __uuidof(Class);
IID iid = __uuidof(Interface);
...
}
在VC++中,為了與其他編譯器以相同的方式來(lái)進(jìn)行uuid的定義,我們不直接使用__declspec(uuid),而是也定義DEFINE_CLSID, DEFINE_IID宏:
#define DEFINE_CLSID(Class, clsid) \
class __declspec(uuid(clsid)) Class
#define DEFINE_IID(Interface, iid) \
struct __declspec(uuid(iid)) Interface
這樣一來(lái),我們已經(jīng)在所有包含VC++在內(nèi)的支持模板特化技術(shù)的編譯器中,提供了__uuidof關(guān)鍵字。通過(guò)它可以進(jìn)一步簡(jiǎn)化你在C++語(yǔ)言中實(shí)現(xiàn)COM組件的代價(jià)。
附注:關(guān)于本文使用的C++模板的特化技術(shù),詳細(xì)請(qǐng)參閱C++文法方面的書(shū)籍,例如《C++ Primer》。其實(shí)這個(gè)技巧在C++標(biāo)準(zhǔn)庫(kù)——STL中有一個(gè)專門(mén)的名字:traits(萃取),你可以在很多介紹STL的書(shū)籍中見(jiàn)到相關(guān)的介紹。