• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>

            woaidongmao

            文章均收錄自他人博客,但不喜標題前加-[轉貼],因其丑陋,見諒!~
            隨筆 - 1469, 文章 - 0, 評論 - 661, 引用 - 0
            數據加載中……

            枚舉聲明,模板函數特化

            我很樂意解釋我是如何寫就 DumpEnum 的,甚至給出代碼。此間我還將回答第二個問題。不過首先讓我為其他讀者解釋一下 DumpEnum 是做什么的。在四月發表的文章中,我要做的一件事情是寫一個 C++ 枚舉來完全充當 .NET 框架類型 RegexOptionsRegexOptions 是一個枚舉類型,你可以用 Regex::Match Replace 之類的方法來控制匹配。例如,你可以用 RegexOptions::IgnoreCase 來調用 Regex::Match 以忽略大小寫,或者
            RegexOptions::Singleline
            來將輸入串作為一行。RegexOptions 的值如 Figure 1 所示。
              為了從本機C++代碼傳遞 RegexOptions,你需要一個具有相同值的 C-式樣枚舉。如果你僅僅是一時之需,那么編寫這樣的代碼最快的方法是從文檔中將之拷貝到你的 C++ 文件,然后按照 C 的語法編輯它。但如果你要進行印刷,或修改選項,或者想要包裝若干枚舉類型的話,那么最好最可靠的方法是寫一個工具,它能自動生成 C++ 代碼——尤其反射使之更容易;.NET 框架提供了全部描述自身所需的信息。所以我寫了一個小程序 DumpEnum,你可以從命令行運行它,像這樣:

            DumpEnum RegexOptions

            DumpEnum 將名字/值對寫成 C/C++ 代碼送到標準輸出。你可以像下面這樣將輸出重定向到一個文件:

            DumpEnum RegexOptions > regopt.h

              然后將 regopt.h 插入到你的頭文件。在我的文章中,RegexWrap.h 就是這么做的。DumpEnum RegexOptions 實際生成的文件如 Figure 2 所示。
              現在你知道 DumpEnum 是做什么的了,下面你會明白我是如何實現它的。

              每一個框架類都是由 System.Type 類描述。它具備 Name 屬性以獲取類型名,IsEnum 告之該類型是否為枚舉。DumpEnum 要做的第一件事情是不論什么類型名,都要獲得 Type,把它們傳遞到命令行——例如,RegexOptions,實現它并不是想像的那么容易。獲得 Type 最普通的兩個方法是:如果已知對象實例,則調用 obj->GetType;否則用 __typeof。例如:

            #using <System.dll>
            using namespace System::Text::RegularExpressions;
            ...
            Type *t = __typeof(RegexOptions);

              在 C++ 中使用托管類型必須用 __typeof,因為 typeof 已經有定義(在新的 C++\CLI 中要用 typeid<>)。但 __typeof 只能用于符號名,而不是字符串,這意味著編譯時你得知道名字,以及哪個程序集和其所屬的名字空間。DumpEnum 在編譯時不具備這些信息,所以要從命令行參數中獲得類型名。DumpEnum 也不具備對象實例,所以它無法調用 Object::GetType,怎么辦呢?
            還有另一個得到 Type 的方法。如果已知該類型屬于哪個程序集,那么可以加載它并調用 Assembly::GetType,它有一個串參數。但 Assembly::GetType 需要完整的類型名,并且你得知道要加載哪個程序集。DumpEnum 給我的第一個痛是要我在命令行敲入下面這些信息:

            DumpEnum System.dll System.Text.RegularExpressions.RegexOptions

              你知道 RegexOptions System.dll 中,而且你也知道名字空間是 System.Text.RegularExpressions,因為文檔就是這么說的。看文檔是一件很惱人的事,而且敲入這個完整的名字空間足以讓你得上腕關節綜合癥。我寧愿花三小時寫一個只需敲12個字符而不是54個字符的程序,而不原意花30秒鐘來查找DLL/namespace和敲入完整類型名。這似乎與懶惰者有些自相矛盾,但這是好程序員的精神特質所需要的,因為你最終是要做出一個很棒的工具永久使用——并且能夠與朋友們分享!
              在按照我的想法寫 DumpEnum 之前,必須解決幾個基本問題,其中包括本文的第二個提問:已知類型名(可能是非限定名),如:RegexOptions,如何知道哪個框架 DLL 包含該類型?沒有集中的數據庫或保存此信息的注冊表項(這也是一件好事情),或方法調用,看來是沒有希望了。但是等一等——再想想!所有的框架 DLLs 都在一個文件夾中,我最近一次數過也就七十來個。為什么強力使用之呢?只要將每個程序集加載到框架環境,查找到那個名字與期望的類型名匹配者。這樣很簡單直白,并且在我低檔的 1 GHz P3 上只需幾秒鐘。我寫了一個程序叫 FindType 就是做此事的:

            FindType MenuItem

            如圖 Figure 3 所示。FindType 列出所有名字中帶 MenuItem 類型輸出的框架 DLLsFindType 查找包含該單詞文本的類型。換句話說,如果你運行“FindType Control”,那么 FindType 將輸出 System.Windows.Forms.Control System.Web.UI.Control,但不包括 System.Web.UI.WebControls.WebControl。這個思路假設你輸入的是簡稱名字,也就是你在 #using 指令中使用的名字空間。FindType 通過建立“\bControl\b”這樣的正則表達式來實現。特殊的錨點字符(原子零寬度斷言)''\b''用于單詞分隔,而不用再匹配中包含分隔符。

            clip_image001
            Figure 3 FindType
            的運行結果

            FindType
            是如何知道要搜索哪個 DLLs 呢?所有框架 DLLs 都在一個名叫“C:\WINDOWS\Microsoft.NET\Framework\v1.1.4322”的文件夾中。你可以通過環境變量 %FrameworkDir% (實際解析出來是“C:\WINDOWS\Microsoft.NET\Framework”)和 %FrameworkVersion%“v1.1.4322”)來獲取自己機器上實際路徑。為了簡單起見,我在 CFindType 類中將此過程進行了封裝。為了使用這個類,你得派生自己專用的特例并實現虛擬函數 OnMatch

            class CMyFindType : public CFindType {
            protected:
               virtual BOOL OnMatch(LPCTSTR typName, LPCTSTR asmPath)
               {
                  // print it
               }
            };     

            然后實例化并調用 FindExact 方法:

            CMyFindType ft;
            int nFound = ft.FindExact("MenuItem");

              CFindType 遍歷程序集,查找與傳入的名字相匹配的類型。只要找到一個匹配類型,便用該類型和程序集名調用你的虛擬 OnMatch 處理例程。如果你想用不同的正則表達式,你可以調用 CFindType::Find 代替 FindExactCFindType 類使 FindType 程序的實現變得容易:只要寫一個 OnMatch,將信息輸出到標準輸出 stdout 即可。具體細節請參考本文附帶的源代碼。
              CFindType 本身派生于一個更為一般的類,CEunmTypes,它枚舉框架中所有的類型,針對每個類型調用虛擬 OnType 函數。CFindType::OnType Regex 比較類型名和請求的名字,并調用 OnMarch 檢查其是否匹配。CEnumTypes _tgetenv 來獲得環境變量以建立型如“FrameworkDir\FrameworkVersion\*.dll”的文件規范,然后用 MFC CFileFind 類枚舉 DLLsCEunmTypes 試圖將每個 DLL 作為程序集加載。如果加載失敗(也許該 DLL 不是一個托管程序集),CEnumTypes 則忽略它并繼續搜索。如果加載成功,便調用 Assembly::GetExportedTypes 來獲取程序集輸出的 Type 數組,然后針對每個 Type 調用 OnType。代碼如 Figure 4 所示。
              現在有了 CFindType,我終于可以用它來解決最初的問題:修改 DumpEnum,以便我不用非得告訴它程序集和完整的類型名。DumpEnum 以不同的 OnMarch 處理例程使用 CFindTypeDumpEnum 中的處理例程檢查該類型確實在枚舉的類型中(Type::IsEnum 返回 True),如果真是如此,則按照 C++ 代碼方式取出枚舉名/值對,如果 Figure 2 所示。DumpIt 函數實際的工作內容如 Figure 5 所示。DumpIt Enum::GetUnderlyingType 獲取枚舉的底層類型(一般是 System.Int32),用 Enum::GetValues 獲取枚舉值,用 Convert::ChangeType 將枚舉值轉換為其對應的底層類型。以下是輸出顯示名字/值對的代碼:

            Type* entype = // 托管枚舉類型
            Array* values = Enum::GetValues(entype);
            for (int i=0; iLength; i++) {
                Object* enval = values->GetValue(i);
                Object* unval = Convert::ChangeType(enval, untype);
                _tprintf(_T("\t%s = %s,\n"), enval, unval);
            }

              現在你一舉兩得:FindType 使你能找到某個特定類型包含在哪個 DLL/程序集;DumpEnum 生成 C/C++ 代碼來包裝框架枚舉類型。
              繼續討論之前,本文有一個小插曲,我寫完 FindType 之后,發現一個令人沮喪的事情,在 Visual Studio .NET 中已經附帶有一個 FindType 程序,甚至名字都一樣!微軟版本的 FindType 所做的事情和我的相同,并且做得更好。(老天啊,微軟的老大們捷足先登;我暈倒!)
              微軟的 FindType 具備選項來指定完全或部分匹配要搜索的目錄和名字空間,是否顯示方法,屬性,事件等等。Figure 6 是它列出所有選項的幫助屏幕:

            clip_image002
            Figure 6 Visual Studio
            附帶的 FindType

              如果你想了解更多關于反射,程序集和類型的內容,或者自在周五晚上無所事事,FindType 是個很好的學習研究例子。你可以在 VS.NET\SDK\v1.1\Samples\Applications\TypeFinder 目錄找到它的源碼,它是用 C# Visual Basic 寫的。我生成了 C# 版本,并將其 EXE 文件拷貝到我的 bin 目錄;現在我都是用 FindType 來查找類型的。
              是不是有了微軟的 FindType,我自己的版本就毫無用處了呢?當然不是。例如,CFindType CEnumTypes
            是類,不是程序,也就是說你可以用它們來寫自己的查找類型信息的工具以及像 DumpEnum 這樣的程序。況且我的類是用中最好的編程語言 C++ 寫的。而微軟的版本用的則是 Visual Basic C#。如果你決定在自己打大應用程序中使用 CFindType,那么應該注意到 CFindType 不用在枚舉時考慮卸載程序集。因為每個程序集都會消耗相當多的內存,你可能會考慮修改這個實現,將每個程序集加載到其自己的應用程序域中,然后在完成程序集的搜索之后卸載應用程序域。


            clip_image003我正在用一個基于模板的庫源代碼,該庫包含一些針對特定類型的模板函數特化。類模板,函數模板和模板函數特化都在頭文件中。我在我的.cpp文件中 #include 頭文件并編譯鏈接工程。但是為了在整個工程中使用該庫,我將頭文件包含在 stdafx.h 中,結果出現特化模板函數的符號多重定義錯誤。我要如何組織頭文件才能避免多重符號定義錯誤?我用 /FORCE:MULTIPLE,但我想用一個更好的解決方法。

            Lee Kyung Jun


            clip_image004
            實際上,確實用更好的解決方法。稍后我會解釋,但首先讓我重溫一下模板函數特化是如何工作的。假設你有一個比較兩個基于 operator> operator== 對象的模板函數:

            template <typename T>
            int compare(T t1, T t2)
            {
               return t1==t2 ? 0 : t1 > t2 ? 1 : -1;
            }

              該模板根據地一個參數是否等于、大于、或小于第二個參數而分別返回零或+/-1。它是典型的用于集合排序時的排序函數。它假設類型 T 具備 operator== operator> 操作,并支持 intfloatdouble DWORD 類型。但它不能應用于比較自負串(char* 指針),因為這個函數比較的是串指針,而不是字符串本身:

            LPCTSTR s1,s2;
            ...
            int cmp = compare(s1,s2); // s1<s2? Oops!

            為了能進行字符串比較,你需要一個使用 strcmp 或其 TCHAR 版本 _tcscmp 的模板特化:

            // specialization for strings
            template<>
            int compare<LPCTSTR>(LPCTSTR s1, LPCTSTR s2)
            {
                return _tcscmp(s1, s2);
            }

              沒錯,這樣做完全正確,現在的問題是:將這個特化放在何處?顯然是要放在模板的頭文件中。但這樣會導致符號多重定義的錯誤,就像 Lee 遇到的那樣。原因很明顯,模板特化是一個函數,而非模板。它與下面的寫法是一樣的:

            int compare(LPCTSTR s1, LPCTSTR s2)
            {
                return _tcscmp(s1, s2);
            }

              沒有理由不在頭文件中定義函數——但是一旦這樣做了,那么你便無法在多個文件中 #include 該頭文件。至少,肯定會有鏈接錯誤。怎么辦呢?
              如果你掌握了模板函數特化即函數,而非模板的概念,你就會認識到有三個選項,完全與普通函數一樣;特化為 inlineextern 或者 static。例如,像下面這樣:

            template<>
            inline int compare<LPCTSTR>(LPCTSTR s1, LPCTSTR s2)
            {
                return _tcscmp(s1, s2);
            }

              對于大多數模板庫而言,這是最容易和最常見的解決方案。因為編譯器直接擴展內聯函數,不產生外部符號,在多個模塊中 #include 它們沒有什么問題。鏈接器不會出錯,因為不存在多重定義的符號。對于像 compare 這樣的小函數來說,inline 怎么說都是你想要的(它更快)。
              但是,如果你的特化很長,或出于某種原因,你不想讓它成為 inline,那要如何做呢?此時可以做成 extern。語法與常規函數一樣:

            // in .h header file
            template<>
            extern int compare<LPCTSTR>(LPCTSTR s1, LPCTSTR s2);

              當然,你得在某個地方實現 compare。部分細節如 Figure 7 所示。我在單獨的模塊 Templ.cpp 中實現了特化,它與主工程鏈接。Templ.h #include stdafx.h 中,而 stdafx.h 又被 #include Templ.cpp 和主模塊兩個文件中——生成工程沒有鏈接錯誤。去下載源代碼自己嘗試一下吧。
              如果你正在為其他開發人員寫模板庫,extern 方式會很不爽,因為你必須創建一個帶目標模塊的鏈接庫(lib),它包含有特化。如果你已經有了一個這樣的 .lib,也沒什么;如果沒有,你可能會想方設法避免引入這樣的庫。僅用頭文件實現模板是更好的方法(麻煩少)。最容易的方式是用 inline,此外,你還能將你的特化放在單獨的頭文件中,使之與其聲明分開并要其他開發人員只在一個模塊中 #include 特化。還有一個可選的方法是將所有東西放在一個文件中,并用預處理符號控制實例化:

            #ifdef MYLIB_IMPLEMENT_FUNCS
            template<>
            int compare<LPCTSTR>(LPCTSTR s1, LPCTSTR s2)
            {
                return _tcscmp(s1, s2);
            }
            #endif

              使用該方法,所有模塊都包含此頭文件,但在包含它之前,只有一個 #define MYLIB_IMPLEMENT_FUNCS。這個方法不支持預編譯頭,因為編譯器用 stdafx.h 中的任何 MYLIB_IMPLEMENT_FUNCS 值加載預編譯版本。
              避免符號多重定義錯誤的最后同時也是用得最少的一個方法是將特化做成 static

            template<>
            static int compare<LPCTSTR>(LPCTSTR s1, LPCTSTR s2)
            {
                return _tcscmp(s1, s2);
            }

              這樣鏈接器也不會出錯,因為靜態函數不向外界輸出其函數,并且它讓你將所有東西都保持在一個頭文件中,不用引入預處理符號。但它缺乏效率,因為每個模塊都有一個函數拷貝。如果函數小到沒什么——那為何不用內聯呢?
              所以簡言之:將特化做成 inline extern。通常都是用 inline。兩種方法都得編輯頭文件。如果使用的是第三方的庫沒有頭文件,那么你除了用鏈接選項 /FORCE:MULTIPLE 之外別無選擇。在你等著生成你的工程時,你可以告訴編寫庫文件的那個家伙——為什么要將函數模板特化定義成 inline 或者 extern。就說是我說的。

            您的提問和評論可發送到 Paul 的信箱:cppqa@microsoft.com.
             

            posted on 2008-10-13 11:25 肥仔 閱讀(1579) 評論(0)  編輯 收藏 引用 所屬分類: C++ 模板

            久久天天躁狠狠躁夜夜av浪潮 | 久久亚洲欧美国产精品| 久久人人爽人人爽人人AV东京热| 久久久久久久97| 久久精品一区二区三区中文字幕| 四虎国产精品成人免费久久| 久久国产精品-国产精品| 无码8090精品久久一区| 国产精品久久久久久福利漫画| 欧美午夜A∨大片久久 | 久久99精品国产99久久| 免费久久人人爽人人爽av| 久久精品9988| 亚洲狠狠婷婷综合久久蜜芽| 久久国产精品二国产精品| 精品国产VA久久久久久久冰| 久久精品女人天堂AV麻| 久久精品www| 97久久香蕉国产线看观看| 久久人人爽人人爽人人片AV高清| 91久久成人免费| 久久精品国产亚洲麻豆| 国产成年无码久久久久毛片| 亚洲精品无码久久久久| 久久久久久久综合狠狠综合| 亚洲国产成人精品无码久久久久久综合| 精品久久久久久久| 久久久国产乱子伦精品作者| 久久久久人妻一区精品色| 久久se精品一区精品二区国产| av午夜福利一片免费看久久| 伊人久久综合无码成人网| 久久综合一区二区无码| 久久精品亚洲欧美日韩久久| 精品久久久久久国产三级| 国产精品99久久久久久董美香| 久久这里只有精品首页| 99久久精品免费看国产| 久久精品国产免费| 精品乱码久久久久久夜夜嗨 | 日本高清无卡码一区二区久久|