VC++ 6.0的小花招

來源:互聯網
上載者:User
VC++ 6.0的小花招  許式偉 2006-2-18 Visual Studio系列中產品中,Visual Studio 6.0是最經典的一個版本,雖然後來有Visual Studio .NET 2003,以及2005,也確實添加了很多讓我覺得激動的特性,但是從使用細節的細膩程度上來看,VS 6.0無疑是最棒的。我們一些同事甚至試圖把2005的C++編譯器獨立的拿到Visual Studio 6.0中來用,也不願意升級到.NET上來用,可見其魅力。 和VS 6.0這個產品的成熟相比,VC++ 6.0的編譯器的的確確相對來說有些糟糕,其中最被詬病的是對模板支援人員很不好。下面我想做的一件事情,就是向那些繼續留戀VC++ 6.0的朋友,介紹一些小花招,來避開VC++ 6.0的一些編譯器缺陷。  1)for (type var=expression;;) 中變數var的範圍問題。 按照C++標準,這裡定義的變數var出了for迴圈應該被銷毀。也就是說下面這段代碼是有效:    for (int i = 0; i < 100; ++i)       func();   for (int i = 0; i < 100; ++i)       func2(); 而下面這段代碼應該編譯不過:     for (int i = 0; i < 100; ++i)    {         if (has_found_it())         {             handle_find_result();             break;          }    }    if (i == 100)         do_not_found(); 然而VC++ 6.0對於第一段代碼會報變數i重複定義錯誤,而第二段代碼編譯通過。 為了讓VC++ 6.0的for語句看起來符合C++標準,你可以這樣做:    #define for if (0); else for 你會發現很有趣,這樣define一下後,VC++ 6.0的for語句完全符合C++標準了!而且由於編譯器的最佳化,Release版本不會增加任何額外的開銷。 喜歡“鑽牛角尖”的朋友可能會說:嗯,不錯的主意。但是——為什麼不這樣做:    #define for if (1) for 嗯?看起來也可以。還是讓我們看一個用例:    if (cond)      for (int i = 0; i < 100; ++i)          func1();   else       func2(); 進行宏代碼展開後,成為:    if (cond)       if (1)           for (int i = 0; i < 100; ++i)               func1();       else           func2(); 這個結果顯然不能符合我們的原意。這裡func2();語句永遠得不到執行機會。  

2)模板參數類型如果不出現在參數列表中,則不能作為傳回值類型。

 由於編譯器的缺陷,VC++ 6.0不支援以下這種用法:    template <class T1, class T2>   T1 func(T2 arg)   {       T1 var;       ... // 處理var過程       return var;   }    void test()   {       int result1 = func<int>(1);       double result2 = func<double>(2);   }; 很抱歉,這種用法VC++ 6.0不支援。讓人惱火的是,VC++ 6.0編譯時間不會提示錯誤,但是產生的執行代碼卻很成問題。 究其原因,是因為VC++ 6.0的template技術是在編譯器的較高層次做的,真正的編譯器核心並不考慮模板。以上面的代碼為例,對編譯器核心來說,只是有兩個重載函數而已:    int func(int arg);   double func(int arg); 如果是普通情況,只是傳回值不同的函數,是不能同時存在的,編譯器應該認為這是一個錯誤。但是很在模板情況下,這兩個函數被簡單認為是同一個函數。因為VC++ 6.0會為每個函數根據它的:   1)所在的namespace;   2)所在的類的類名(如果是成員函數);   3)函數名;   4)函數調用方式(cdecl、stdcall還是fastcall);   5)所有參數的類型;而產生一個唯一標識該函數的函數名。這個過程叫Name Mangling,是所有C++編譯器都要進行的工作。而另一個背景是,很多C++編譯器產生的目標檔案(.obj檔案)有一些和模板相關的特殊資訊,包括也標識了某個函數是否模板函數。這是因為一個模板函數在多個源檔案(.cpp檔案)中被調用的話,這個模板函數就會在這些源檔案編譯產生的目標檔案(.obj檔案)中都定義(definition)一份。為了支援模板,link程式顯然必須知道這個函數是模板函數,從而隨意選擇一個定義(丟棄其餘的定義),而不是報符號重複定義錯誤。 因為函數名、參數列表等完全一致,所以這兩個函數Name Mangling後產生的名字是一樣的,並且,它們都被標識為這是模板函數。從而,link程式在工作的時候,簡單地將其中一個函數定義給拋棄了。 那麼,如果我們非要提供上述的func函數,怎麼辦?我們來點花招: template <class T1>class func{private:    T1 var; public:    template <class T2>    func(T2 arg)    {       ... // 處理var過程    }    operator T1() const    {        return var;    }}; 我們再來使用func這個“函數”:    void test()   {       int result1 = func<int>(1);       double result2 = func<double>(2);   }; 呵呵,你會發現,它還真象是你期望的正常工作。  3)模擬VC++提供的關鍵字__uuidof。 這個技巧不是針對VC++ 6.0缺陷的,而是針對VC++擴充文法的。這個技巧的來由,是為了某些希望有一天有可能要脫離Visual C++環境進行開發的人員。為了脫離VC++,你需要謹慎使用它的所有擴充文法。例如本文討論的__uuidof。我們先來看看一個例子: 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的方法簡單多了吧?可惜,這樣好用的東西,它只在VC++中提供。不過沒有關係,我們這裡介紹一個技巧,可以讓你在幾乎所有C++編譯器中都可以這樣方便的使用__uuidof。這裡沒有說是所有,是因為我們使用了模板特化技術,可能存在一些比較“古老”的C++編譯器,不支援該特性。 也許你已經迫不及待了。好,讓我們來看看: #include <string>#include <cassert> inlineSTDMETHODIMP_(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)  這樣一來,就已經類比出一個__uuidof關鍵字。我們可以很方便進行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++中,為了與其他編譯器以相同的方式來進行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 這樣一來,我們已經在所有包含VC++在內的支援模板特化技術的編譯器中,提供了__uuidof關鍵字。通過它可以進一步簡化你在C++語言中實現COM組件的代價。 附註:關於本文使用的C++模板的特化技術,詳細請參閱C++文法方面的書籍,例如《C++ Primer》。其實這個技巧在C++標準庫——STL中有一個專門的名字:traits(萃取),你可以在很多介紹STL的書籍中見到相關的介紹。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.