原文出處: codeproject 譯文出處: oschina介紹Visual C++ 2013 Preview 在6月發(fā)布了,C++開發(fā)者又找到一個編譯器可以更好的支持ISO C++ 11 的特性了。本文介紹了這些新的特性并附有代碼實例。 你想動手嘗試編譯文中的這些代碼話,需要去下載并安裝Visual Studio 2013 Preview,我尚未在其他編譯器上測試這些代碼,所以我并不知道與Gcc 或Clang的兼容性(可惡的C++)。 原始字符串字面值VC++ 2013現(xiàn)在支持原始字符串字面值了。注意:它并不支持統(tǒng)一碼字符串字面值。一個原始字符串字面值允許你避免轉(zhuǎn)義那些在HTML,XML和正則表達(dá)式里運用得得心應(yīng)手的特殊字符。下面是一個示例用法: auto s1 = R"(This is a "raw" string)"; 現(xiàn)在,s1是一個指向常量字符串值為“This is a “raw” string”的指針。盡管不支持嵌套雙引號,這與C#支持的@string文字是類似的。那么要在一個字符串字面值中嵌入R”(…)”會怎樣。這種情況下,你可以使用以下語法: auto s2 = R"QQ(Example: R"(This is my raw string)")QQ"; 現(xiàn)在,s2包含 Example: R”(This is my raw string)”。 在這個例子中,我把QQ作為界定符。這個界定符可以是任何長度不超過16的字符串。原始字符串字面值也可以包含換行: auto s3 = R"(<tr> <td>data</td> </tr>)"; 最后,不論他們什么時候添加統(tǒng)一碼字符串字面值的支持,你都可以將它們連接起來并構(gòu)成原始統(tǒng)一碼字符串字面值。 可變參數(shù)模板可變參數(shù)模板是一個允許多個參數(shù)的模板。在我看來,這是個提供給庫作者而不是給庫使用者的特性,所以我也不是很確定它在C++程序員中會有多流行。以下我們用一個非常簡單的例子來展示如何在實際開發(fā)中使用可變參數(shù)模板。 // Variadic template declaration template<typename... Args> class Test; // Specialization 1 template<typename T> class Test<T> { public: T Data; }; // Specialization 2 template<typename T1, typename T2> class Test<T1, T2> { public: T1 Left; T2 Right; }; void Foo() { Test<int> data; data.Data = 24; Test<int, int> twovalues; twovalues.Left = 12; twovalues.Right = 15; } 當(dāng)使用可變參數(shù)模板時,智能感應(yīng)(intellisense)能很好地配合我們的開發(fā)??勺儏?shù)模板的實現(xiàn)包括一個叫asizeof的函數(shù),這個函數(shù)能返回這個模板的參數(shù)個數(shù)。 template<typename... Args> class Test { public: size_t GetTCount() { return sizeof...(Args); } }; // . . . Test<int> data; size_t args = data.GetTCount(); //1 Test<int, int, char*> data2; args = data2.GetTCount(); //3 Test<int, float> data3; args = data3.GetTCount(); //2 這其實就是一個數(shù)個數(shù)的例子,但我猜他們之所以使用一個現(xiàn)存的函數(shù)名是因為這樣子做會讓C++程序員們更容易上手。 對于可變參數(shù)模板,一個常用的做法就是專攻其中一個參數(shù),然后把其余的參數(shù)都變?yōu)榭蛇x。這個做法可以以遞歸的形式實現(xiàn)。以下是一個比較傻的例子,但它能讓你明白什么時候不應(yīng)該用可變參數(shù)模板,繼而更好地了解這個語言特性。 template<typename... Args> class Test; // Specialization for 0 arguments template<> class Test<> { }; // Specialization for at least 1 argument template<typename T1, typename... TRest> class Test<T1, TRest...> : public Test<TRest...> { public: T1 Data; // This will return the base type Test<TRest...>& Rest() { return *this; } }; void Foo() { Test<int> data; data.Data = 24; Test<int, int> twovalues; twovalues.Data = 10; // Rest() returns Test<int> twovalues.Rest().Data = 11; Test<int, int, char*> threevalues; threevalues.Data = 1; // Rest() returns Test<int, int> threevalues.Rest().Data = 2; // Rest().Rest() returns Test<char*> threevalues.Rest().Rest().Data = "test data"; } 大家請注意了,千萬別把代碼寫成這樣。這個例子僅僅是用來教學(xué)的,正確的做法我會在下一個章節(jié)中告訴大家。 Tuple的實現(xiàn)我們來看一下std tuple的頭文件 (由VC++團(tuán)隊的Stephan T. Lavavej負(fù)責(zé)維護(hù) – 最初的代碼由P.J. Plauger編寫),瀏覽這些代碼,讓我的大腦幾乎要宕掉了。為了更好的理解代碼,我將代碼進(jìn)行簡化,摘出其中可以訪問tuple的值的最少的代碼(能夠支持讀和寫)。這有助于理解在設(shè)計模板類時,通??勺儏?shù)模板是如何通過遞歸展開來大幅減少代碼的行數(shù)。 // tuple template<class... _Types> class tuple; // 空tuple template<> class tuple<> {}; // 遞歸的tuple定義 template<class _This, class... _Rest> class tuple<_This, _Rest...> : private tuple<_Rest...> { public: _This _Myfirst; }; 這里的遞歸特化使用了繼承,因此tuple的每個類型成員都確定的時候遞歸會終止。讀取tuple值的時候,tuple_element類起到讀取輔助類的作用。 // tuple_element template<size_t _Index, class _Tuple> struct tuple_element; // select first element template<class _This, class... _Rest> struct tuple_element<0, tuple<_This, _Rest...>> { typedef _This& type; typedef tuple<_This, _Rest...> _Ttype; }; // recursive tuple_element definition template <size_t _Index, class _This, class... _Rest> struct tuple_element<_Index, tuple<_This, _Rest...>> : public tuple_element<_Index - 1, tuple<_Rest...> > { }; 這里又一次使用了遞歸繼承,同時邊界條件也特化了。注意這里的兩個typedef,其中一個定義為對應(yīng)值類型的引用,另一個定義為和tuple_element類型參數(shù)相同的tuple類型。因此,給定一個_Index值,在那個遞歸層次上我們就能得到對應(yīng)tuple的類型和tuple值的類型。下面的get方法就使用了這個特性。 // get reference to _Index element of tuple template<size_t _Index, class... _Types> inline typename tuple_element<_Index, tuple<_Types...>>::type get(tuple<_Types...>& _Tuple) { typedef typename tuple_element<_Index, tuple<_Types...>>::_Ttype _Ttype; return (((_Ttype&) _Tuple)._Myfirst); } 注意返回類型,它使用上面定義的類型 typedef。同樣,元組(tupleis)轉(zhuǎn)換為上述定義過的類型 _TType ,然后我們訪問 _Myfirst 成員 (它表示的值)?,F(xiàn)在你可以如下所示,編寫代碼, tuple<int, char> t1; get<0>(t1) = 959; get<1>(t1) = 'A'; auto v1 = get<0>(t1); auto v2 = get<1>(t1); 現(xiàn)在 , 這 不用 說 , 但 我會 說 只是 可以 肯定 的是 —— 這 里只 是 為了 演示 。 不 要在 實際 代碼 中 使用 這些 方法, 而是調(diào)用 std::tuple, 它可以完成比 這 一切多的功能 ( 這就是為什么他有800行長). 代理構(gòu)造函數(shù)代理構(gòu)造函數(shù)已經(jīng)在C#中用了好長時間,所以將其引入到C++中也很不錯。編譯器允許一個類型的構(gòu)造函數(shù)(代理構(gòu)造函數(shù))在其初始化列表中包含另一個構(gòu)造函數(shù)。以前編寫代碼形式如下: class Error { public: Error() { Init(0, "Success"); } Error(const char* message) { Init(-1, message); } Error(int errorCode, const char* message) { Init(errorCode, message); } private: void Init(int errorCode, const char* message) { //... } }; 采用代理構(gòu)造函數(shù)是,可以寫成如下形式: class Error { public: Error() : Error(0, "Success") { } Error(const char* message) : Error(-1, message) { } Error(int errorCode, const char* message) { // ... } }; 相關(guān)閱讀-Herb Sutter和Jim Hyslop在十年前(2003年5月)寫的一篇有趣的關(guān)于代理構(gòu)造函數(shù)的文章。
函數(shù)模板中的默認(rèn)模板參數(shù)這是VC++ 2013現(xiàn)在支持的另一項C++ 11特性。目前為止,下面的代碼仍然無法通過VC++編譯。 template <typename T = int> void Foo(T t = 0) { } // error C4519: default template arguments are only // allowed on a class template Visual C++ 2013 能夠順利編譯這些代碼,模板參數(shù)推斷也能正確進(jìn)行。 Foo(12L); // Foo<long> Foo(12.1); // Foo<double> Foo('A'); // Foo<char> Foo(); // Foo<int> 這項特性的實用性在下面的例子里尤為明顯。 template <typename T> class Manager { public: void Process(T t) { } }; template <typename T> class AltManager { public: void Process(T t) { } }; template <typename T, typename M = Manager<T>> void Manage(T t) { M m; m.Process(t); } Manage(25); // Manage<int, Manager<int>> Manage<int, AltManager<int>>(25); // Manage<int, AltManager<int>> 并不是所有的參數(shù)都需要默認(rèn)參數(shù)。 template <typename B, typename T = int> void Bar(B b = 0, T t = 0) { } Bar(10); // Bar<int, int> Bar(10L); // Bar<long, int> Bar(10L, 20L); // Bar<long, long> Bar(); // will not compile 如果帶默認(rèn)參數(shù)的函數(shù)模板有重載的話,類型無法推斷的時候編譯器將會給出錯誤。 template <typename T = int> void Foo(T t = 0) { } template <typename B, typename T = int> void Foo(B b = 0, T t = 0) { } Foo(12L); // will not compile Foo(12.1); // will not compile Foo('A'); // will not compile Foo(); // Foo<int> 使用函數(shù)模板的默認(rèn)模板參數(shù)時應(yīng)當(dāng)在這里注意。 顯式轉(zhuǎn)換運算符我仍然記得2004年八月的一天,那個時候我意識到盡管我是一個還不錯的C++程序員,我對explicit關(guān)鍵字一無所知,這令我十分局促不安。那之后我寫了一篇博客文章
簡單說明一下explicit的使用??紤]一下下面的例子。 class Test1 { public: explicit Test1(int) { } }; void Foo() { Test1 t1(20); Test1 t2 = 20; // will not compile } 盡管轉(zhuǎn)換構(gòu)造函數(shù)可以達(dá)到這一目的,轉(zhuǎn)換運算符因為缺乏標(biāo)準(zhǔn)支持而無法完成類似的任務(wù)。壞消息是你無法確保轉(zhuǎn)換構(gòu)造函數(shù)和轉(zhuǎn)換運算符的行為是一致的??紤]一下下面的例子。 class Test1 { public: explicit Test1(int) { } }; class Test2 { int x; public: Test2(int i) : x(i) { } operator Test1() { return Test1(x); } }; void Foo() { Test2 t1 = 20; Test1 t2 = t1; // will compile } 上面的代碼能通過編譯?,F(xiàn)在有了C++ 11的新特性,explicit也可以用在轉(zhuǎn)換運算符上了。 class Test2 { int x; public: Test2(int i) : x(i) { } explicit operator Test1() { return Test1(x); } }; void Foo() { Test2 t1 = 20; Test1 t2 = (Test1)t1; // this compiles Test1 t3 = t1; // will not compile } 下面的這個例子里隱式應(yīng)用了bool類型的轉(zhuǎn)換運算符。 class Test3 { public: operator bool() { return true; } }; void Foo() { Test3 t3; if (t3) { } bool b = t3; } 這段代碼能通過編譯?,F(xiàn)在試一下在運算符上加上explicit關(guān)鍵字 class Test3 { public: explicit operator bool() { return true; } }; void Foo() { Test3 t3; if (t3) // this compiles! { } bool b = t3; // will not compile } 正如預(yù)期,第二個轉(zhuǎn)換無法通過編譯,但是第一個通過了。這是因為if里的bool轉(zhuǎn)換被視為顯式轉(zhuǎn)換。因此在這里你要小心謹(jǐn)慎,僅僅添加explicit關(guān)鍵字無法防止意外情況下的類型轉(zhuǎn)換,類型可能仍然是不安全的。 初始化列表和統(tǒng)一初始化一直以來我們都可以用初始化列表初始化數(shù)組,現(xiàn)在對于有類型為std::initializer_list<T>(包含構(gòu)造函數(shù))的類型我們也可以這么做。標(biāo)準(zhǔn)庫中的容器現(xiàn)在都支持這一特性。 void foo() { vector<int> vecint = { 3, 5, 19, 2 }; map<int, double> mapintdoub = { { 4, 2.3}, { 12, 4.1 }, { 6, 0.7 } }; } 自己實現(xiàn)這些功能很浪費時間 void bar1(const initializer_list<int>& nums) { for (auto i : nums) { // use i } } bar1({ 1, 4, 6 }); 用戶自定義類型也可以使用這一特性 class bar2 { public: bar2(initializer_list<int> nums) { } }; class bar3 { public: bar3(initializer_list<bar2> items) { } }; bar2 b2 = { 3, 7, 88 }; bar3 b3 = { {1, 2}, { 14 }, { 11, 8 } }; C++11也新增了一個相關(guān)特性:統(tǒng)一初始化( Uniform initialization)。這一特性將自動匹配合適的構(gòu)造函數(shù) class bar4 { int x; double y; string z; public: bar4(int, double, string) { } }; class bar5 { public: bar5(int, bar4) { } }; bar4 b4 { 12, 14.3, "apples" }; bar5 b5 { 10, { 1, 2.1, "bananas" } }; 使用初始化列表的構(gòu)造函數(shù)將被優(yōu)先使用 class bar6 { public: bar6(int, int) // (1) { // ... } bar6(initializer_list<int>) // (2) { // ... } }; bar6 b6 { 10, 10 }; // --> calls (2) above 參考資料
參與翻譯:無奈的鈍刀, MtrS, 大志darcy, jimmyjmh, LinuxQueen, soaring 來自jobbole
標(biāo)簽:C++
|
|