VS2013中的C++11新特性

oschina發表於2013-07-26

  Visual C++ 2013 Preview 在6月釋出了,C++開發者又找到一個編譯器可以更好的支援ISO C++ 11 的特性了。本文介紹了這些新的特性並附有程式碼例項。

  你想動手嘗試編譯文中的這些程式碼話,需要去下載並安裝Visual Studio 2013 Preview (話說:付費嗎?),我尚未在其他編譯器上測試這些程式碼,所以我並不知道與Gcc 或Clang的相容性(可惡的C++)。

  原始字串字面值

  VC++ 2013現在支援原始字串字面值了。注意:它並不支援統一碼字串字面值。一個原始字串字面值允許你避免轉義那些在HTML,XML和正規表示式裡運用得得心應手的特殊字元。下面是一個示例用法:

auto s1 = R"(This is a "raw" string)";

  現在,s1是一個指向常量字串值為“This is a "raw" string”的指標。儘管不支援巢狀雙引號,這與C#支援的@string文字是類似的。那麼要在一個字串字面值中嵌入R"(...)"會怎樣。這種情況下,你可以使用以下語法:

auto s2 = R"QQ(Example: R"(This is my raw string)")QQ";

  現在,s2包含 Example: R"(This is my raw string)"。 在這個例子中,我把QQ作為界定符。這個界定符可以是任何長度不超過16的字串。原始字串字面值也可以包含換行:

auto s3 = R"(<tr>
<td>data</td>
</tr>)";

  最後,不論他們什麼時候新增統一碼字串字面值的支援,你都可以將它們連線起來並構成原始統一碼字串字面值。

  可變引數模板

  可變引數模板是一個允許多個引數的模板。在我看來,這是個提供給庫作者而不是給庫使用者的特性,所以我也不是很確定它在C++程式設計師中會有多流行。以下我們用一個非常簡單的例子來展示如何在實際開發中使用可變引數模板。

// 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;
}

  當使用可變引數模板時,智慧感應(intellisense)能很好地配合我們的開發。可變引數模板的實現包括一個叫asizeof的函式,這個函式能返回這個模板的引數個數。

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

  這其實就是一個數個數的例子,但我猜他們之所以使用一個現存的函式名是因為這樣子做會讓C++程式設計師們更容易上手。

  對於可變引數模板,一個常用的做法就是專攻其中一個引數,然後把其餘的引數都變為可選。這個做法可以以遞迴的形式實現。以下是一個比較傻的例子,但它能讓你明白什麼時候不應該用可變引數模板,繼而更好地瞭解這個語言特性。

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";
}

  大家請注意了,千萬別把程式碼寫成這樣。這個例子僅僅是用來教學的,正確的做法我會在下一個章節中告訴大家。

  Tuple的實現

  我們來看一下std tuple的標頭檔案 (由VC++團隊的Stephan T. Lavavej負責維護 - 最初的程式碼由P.J. Plauger編寫),瀏覽這些程式碼,讓我的大腦幾乎要宕掉了。為了更好的理解程式碼,我將程式碼進行簡化,摘出其中可以訪問tuple的值的最少的程式碼(能夠支援讀和寫)。這有助於理解在設計模板類時,通常可變引數模板是如何通過遞迴展開來大幅減少程式碼的行數。

// 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,其中一個定義為對應值型別的引用,另一個定義為和tuple_element型別引數相同的tuple型別。因此,給定一個_Index值,在那個遞迴層次上我們就能得到對應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)轉換為上述定義過的型別 _TType ,然後我們訪問 _Myfirst 成員 (它表示的值)。現在你可以如下所示,編寫程式碼,

tuple<int, char> t1;
get<0>(t1) = 959;
get<1>(t1) = 'A';

auto v1 = get<0>(t1);
auto v2 = get<1>(t1);

  現在 , 這 不用 說 , 但 我會 說 只是 可以 肯定 的是 ------ 這 裡只 是 為了 演示 。 不 要在 實際 程式碼 中 使用 這些 方法, 而是呼叫 std::tuple, 它可以完成比 這 一切多的功能 ( 這就是為什麼他有800行長).

  代理建構函式

  代理建構函式已經在C#中用了好長時間,所以將其引入到C++中也很不錯。編譯器允許一個型別的建構函式(代理建構函式)在其初始化列表中包含另一個建構函式。以前編寫程式碼形式如下:

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)
  {
    //...
  }
};

  採用代理建構函式是,可以寫成如下形式:

class Error
{
public:
  Error() : Error(0, "Success")
  {
  }

  Error(const char* message) : Error(-1, message)
  {
  }

  Error(int errorCode, const char* message)
  {
    // ...
  }
};

  相關閱讀-  Herb Sutter和Jim Hyslop在十年前(2003年5月)寫的一篇有趣的關於代理建構函式的文章。

  函式模板中的預設模板引數

  這是VC++ 2013現在支援的另一項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 能夠順利編譯這些程式碼,模板引數推斷也能正確進行。

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>>

  並不是所有的引數都需要預設引數。

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

  如果帶預設引數的函式模板有過載的話,型別無法推斷的時候編譯器將會給出錯誤。

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>

  使用函式模板的預設模板引數時應當在這裡注意。

  顯式轉換運算子

  我仍然記得2004年八月的一天,那個時候我意識到儘管我是一個還不錯的C++程式設計師,我對explicit關鍵字一無所知,這令我十分侷促不安。那之後我寫了一篇部落格文章

  簡單說明一下explicit的使用。考慮一下下面的例子。

class Test1
{
public:
  explicit Test1(int) { }
};

void Foo()
{
  Test1 t1(20);
  Test1 t2 = 20; // will not compile
}

  儘管轉換建構函式可以達到這一目的,轉換運算子因為缺乏標準支援而無法完成類似的任務。壞訊息是你無法確保轉換建構函式和轉換運算子的行為是一致的。考慮一下下面的例子。

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
}

  上面的程式碼能通過編譯。現在有了C++ 11的新特性,explicit也可以用在轉換運算子上了。

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
}

  下面的這個例子裡隱式應用了bool型別的轉換運算子。

class Test3
{
public:
  operator bool() { return true; }
};

void Foo()
{
  Test3 t3;
  if (t3)
  {
  }

  bool b = t3;
}

  這段程式碼能通過編譯。現在試一下在運算子上加上explicit關鍵字

class Test3
{
public:
  explicit operator bool() { return true; }
};

void Foo()
{
  Test3 t3;
  if (t3) // this compiles!
  {
  }

  bool b = t3; // will not compile
}

  正如預期,第二個轉換無法通過編譯,但是第一個通過了。這是因為if裡的bool轉換被視為顯式轉換。因此在這裡你要小心謹慎,僅僅新增explicit關鍵字無法防止意外情況下的型別轉換,型別可能仍然是不安全的。

  初始化列表和統一初始化

  一直以來我們都可以用初始化列表初始化陣列,現在對於有型別為std::initializer_list<T>(包含建構函式)的型別我們也可以這麼做。標準庫中的容器現在都支援這一特性。

void foo()
{
  vector<int> vecint = { 3, 5, 19, 2 };
  map<int, double> mapintdoub =
  {
    { 4, 2.3},
    { 12, 4.1 },
    { 6, 0.7 }
  };
}

  自己實現這些功能很浪費時間

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也新增了一個相關特性:統一初始化( Uniform initialization)。這一特性將自動匹配合適的建構函式

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" } };

  使用初始化列表的建構函式將被優先使用

class bar6
{
public:
  bar6(int, int) // (1)
  {
    // ...
  }

  bar6(initializer_list<int>) // (2)
  {
    // ...
  }
};
  
bar6 b6 { 10, 10 }; // --> calls (2) above

  就是這些內容。像往常一樣,歡迎反饋和批評建議。謝謝。

  參考資料

  英文來源:http://www.codeproject.com/Articles/623872/Cplusplus-11-features-in-Visual-Cplusplus-2013-Pre

相關文章