1. 程式人生 > 其它 >C++ 11 應用

C++ 11 應用

第一章 使用C++11讓程式簡潔、現代

1.1 型別推導

1.1.1 auto

  1. 為在編譯期推理和替換,使用auto宣告的變數必須馬上初始化
  2. auto推導規則
  • 不宣告 *指標 或 &引用 時,auto推導結果 和 初始化表示式 拋棄引用和cv限定符後 的型別一致;
  • 宣告 *指標 或 &引用 時,auto推導結果 保持 初始化表示式 的cv屬性
    (cv限定符:cv (const and volatile) type qualifiers)
  1. auto的限制:不能用於函式引數func(auto)、非靜態成員變數class::auto、陣列auto[]、模板引數<auto>
  2. 常用auto的情況:型別名過長的 迭代器、map等複合型別返回值、不能確定的型別(部分情況 模板+auto)

1.1.2 decltype

  1. 作用:在編譯時推匯出一個 表示式exp的型別,不捨棄引用/指標和cv限定符。適用情況:通過某表示式宣告型別,又不想按這個表示式的值做初始化。
  2. decltype(exp)推導規則:
  • exp識別符號、類訪問表示式class::member,decltype(exp)exp型別一致
  • exp為 函式呼叫,decltype(exp)和返回值型別一致
  • exp返回結果為 左值,decltype(exp)exp的左值引用:int x; // decltype((x)) 為 int&
  • 純右值prvalue只有類型別可以攜帶cv限定符,其它如基礎型別int可以忽略
  1. 常用decltype的情況:泛型程式設計、抽取冗長的變數型別

1.1.3 返回值後置語法:auto + decltype

示例

template <typename T, typename U>
auto add(T t, U u) -> decltype(t + u)
{
    return t + u;
}
/*****************************************/
int& foo(int& i);
float foo(float& f);
template <typename T>
auto func(T& val) -> decltype(foo(val))
{
    return foo(val);
}

1.2 模板細節改進

1.2.1 C++11可以識別模板巢狀的>>,防止二義性可加括號

1.2.2 使用using可定義<模板>別名 alias template

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

類模板即使都有預設模板引數也要加<>
函式模板可不加<>,且沒有預設引數必須寫在最後的限制(若指定模板引數 直接從左到右填充),指定引數 優於 自動推理 優於 預設引數

1.3 列表初始化 List-initialization

1.3.1 使用{初始化列表}統一初始化方式

{}前有無=不影響初始化型別type var{*}等價於type var={*},除非 type var = type {*} 會在列表初始化匿名物件後,拷貝初始化var。

1.3.2 非聚合型別初始化需定義建構函式

聚合型別定義 Aggregates

  1. 是普通陣列
  2. 型別是一個類,且無自定義建構函式;無private或protect的非靜態資料成員;無基類;無虛擬函式;不能有{}、=進行直接初始化的非靜態資料成員。
    聚合型別的定義非遞迴。

1.3.3 初始化列表 std::initializer_list

initializer_list儲存了{}中物件T的陣列(size為兩個地址長:begin和end指標),長度任意,只能整體初始化或賦值,有size()/begin()/end()三個介面。

1.3.4 列表初始化防止型別收窄(編譯器報error)

1.4 範圍for迴圈

使用 __for_begin(首迭代器) __for_end(尾迭代器) __for_range(迭代容器的引用) 三個變數支援範圍for。若自定義類欲支援範圍for,需實現begin()和end()。
for (const auto& val : exp) exp只在最開始執行一次,遍歷基於迭代器實現,有可能出現迭代器失效,故儘量不要修改迭代的容器。

1.5 通過std::functionbind統一可排程物件的定義,繫結可呼叫物件與其引數

可呼叫物件 callable objects:

  • 函式指標、具有operator()成員的類物件(仿函式)、可被轉換為函式指標的類物件、類成員(函式)指標。
    (函式型別不是可呼叫物件)
    std::bind 可將 callable objects 與引數一起繫結。
    靈活使用 std::placeholders::_1

1.6 lambda表示式

[capture] (params) opt -> ret {body;} 返回值型別可不宣告,由body中的return語句自動推導。
若按值捕獲外部變數[=],值在lambda宣告時即固定

const ret_type operator()(const struct {...} * const __closure);

可指明opt選項為mutable

ret_type operator()(struct {...} * const __closure);

或 按引用捕獲外部變數[&]。
lambda可理解為就地定義仿函式的語法糖,捕獲的外部變數即為成員變數。
沒有捕獲變數的lambda可轉化為函式指標(沒捕獲就不需要this指標)。

1.7 tupe元組

增強版std::pair。實現涉及模板超程式設計。

第二章 使用C++11改程序序效能

2.1 右值引用

2.1.1 右值引用特性


左值右值將亡值概念

An lvalue has an address that your program can access. Examples of lvalue expressions include variable names, including const variables, array elements, function calls that return an lvalue reference, bit-fields, unions, and class members.
A prvalue expression has no address that is accessible by your program. Examples of prvalue expressions include literals, function calls that return a non-reference type, and temporary objects that are created during expression evaluation but accessible only by the compiler.
An xvalue expression has an address that no longer accessible by your program but can be used to initialize an rvalue reference, which provides access to the expression. Examples include function calls that return an rvalue reference, and the array subscript, member and pointer to member expressions where the array or object is an rvalue reference.
右值引用的型別可以是左值也可以是右值(std::move)
編譯器會將已命名的右值引用視為左值(如函式傳入的引數為 右值引用+其名稱(相當於命名),確實會在棧上移動構造value,給分配空間(見2.3 可用std::forward保持左值、右值特徵))
引用摺疊規則:右值引用疊加到右值引用還是右值引用、其它疊加方式變成左值引用。
T&&auto&&(不加const)這種發生型別推斷的情況,叫做universal reference,可變成左值引用或右值引用,必須初始化。

2.1.2 使用右值引用(移動建構函式)避免深拷貝

2.2 move語義

使用std::move將左值轉換為右值引用,從而可通過移動構造 轉移資源控制權

2.3 forward和完美轉發

完美轉發 perfect forwarding:指在函式模板中,完全依照模板的引數型別(保持左值、右值特徵)將引數傳遞給函式模板中呼叫的另一個引數。
可用 universal reference + 完美轉發 + 可變模板引數 實現通用函式包裝器。

2.4 emplace_back 減少記憶體拷貝和移動

push_back() 向容器尾部新增元素時,首先會建立這個元素,然後再將這個元素拷貝或者移動到容器中(如果是拷貝的話,事後會自行銷燬先前建立的這個臨時元素)
emplace_back() 在實現時,則是直接在容器尾部建立這個元素(需直接在括號裡寫構造引數),省去了拷貝或移動元素的過程。

2.5 unordered container 無序容器

unordered_(multi)map/unordered_(multi)set 使用hash表儲存元素(map/set使用紅黑樹)空間換時間。對自定義型別需要實現hash函式和比較函式。

第三章 使用C++11消除重複程式碼

3.1 type_traits 型別萃取

type_traits的型別選擇功能可降低程式的圈複雜度,提高可維護性。

3.1.1 基本用法

  1. 定義編譯期常量
    繼承 std::integral_constant 型別,是下列類的型別(?)

  2. 型別判斷的type_traits
    在標頭檔案<type_traits>中:is_void is_enum is_lvalue_reference is_const is_unsigned is_move_constructible...

  3. 判斷兩個型別之間關係的type_traits Type relationships
    std::integral_constantis_base_of is_convertible ...

  4. 型別轉換的traits Type modifications
    remove(add)_cv/const/volatile
    std::decay 移除引用和cv or 陣列退化為元素 or 函式退化為函式指標
    以一段開原始碼為例:

// get specific T value from a map<string, any>, only if type 'T' matches 'any'
// template <class T, typename U = std::remove_cv<std::remove_reference<T>>>
template <class T, typename U = std::decay<T>>
static const U &GetValue(const std::shared_ptr<std::map<std::string, std::any>> &data, const std::string &key) {
  static const U empty_result{};
  if (data == nullptr) {
    return empty_result;
  }
  auto iter = data->find(key);
  if (iter == data->end()) {
    return empty_result;
  }
  const std::any &value = iter->second;
  if (value.type() != typeid(U)) {
    return empty_result;
  }

  return std::any_cast<const U &>(value);
}
#include <typeinfo>
#include <cxxabi.h>
#include <type_traits>
abi::__cxa_demangle(typeid(decay<decltype(func)>::type).name(), 0, 0, 0)
// func為void()型別,加入decay變為void (*)()

3.1.2 根據條件選擇的traits

std::conditional在編譯期根據一個判斷式選擇兩個型別中的一個,類似於三元表示式

// 原型如下
template<bool B, class T, class F>
struct conditional
// 例如:
using ret_type = std::conditional<std::is_integral<int>::value, long, doube>::type // ret_type為long

3.1.3 獲取可呼叫物件返回型別的traits

std::result_of編譯器獲得可呼叫物件(見1.5)的返回型別

void func() { }

// 下列 A B C D 型別均為 void
using A = std::result_of<decltype(func)&()>::type;
// decltype(func)&() 等價於 void (&())()

// 下列result_of的引數 等價於 remove_reference<remove_cv<remove_reference<void (*)()>::type>::type
using B = std::result_of<decltype(&func)()>::type;
using C = std::result_of<decltype(func)*()>::type;
using D = std::result_of<typename std::decay<decltype(func)>::type ()>::type;

3.1.4 根據條件禁用或啟用某種或某些型別traits

std::enable_if 利用SFINAE(substitution-failure-is-not-an-error)實現編譯期的模板型別限定,若完全找不到匹配則編譯報錯
可用於限定函式返回值、引數型別、

template<typename T>
typename std::enable_if<std::is_arithmetic<T>::value, std::string>::type
func(T t) { };

auto s1 = func(1);      // T is int, return string type
auto s2 = func(2.0);    // T is double, return string type
auto s3 = func("333");  // T is char*, compile error

/********************************************************/
template<class T, class = typename std::enable_if<std::is_floating_point<T>::value>::type>
T func(T t) { return t; }

func(1.0f); // returns float (1.0f)
func(1.0l); // returns long double (1.0l)
func(1);    // compile error 

/********************************************************/
template<class T>
typename std::enable_if<std::is_same<T, std::string>::value, std::string>::type ToString(T& t)
{ return t; }

template<class T>
typename std::enable_if<std::is_arithmetic<T>::value, std::string>::type ToString(T& t)
{ return std::to_string(t); }

3.2 可變引數模板

模板宣告時在template和class後帶上省略號...
省略號...的作用:

  • 宣告引數包,可包含0到任意數量個引數
  • 在模板定義右邊,可將引數包展開為一個個獨立引數

3.2.1 可變引數模板函式

template<class... T>
void func(T... args) {
  std::cout << sizeof...(args) << std::endl; // sizeof...() 可獲得變參的個數
}

func()                    // print 0
func(1, 2, "3");          // print 3
func(1, '2', 3.0f, "4");  // print 4
  1. 遞迴方式展開引數包
    某開原始碼中的一段例子,序列化任意型別和引數的函式
class Serializer {
 public:
  Serializer() = default;
  virtual ~Serializer() = default;

  /*
   * Code function call to generated code
   */
  template <typename... PARAMETERS>
  void CodeFunction(const std::string &name, PARAMETERS... parameters) {
    code << name << "(";
    GenCode(parameters...);
    code << ");\n";
  }

  template <typename T>
  Serializer &operator<<(T t) {
    code << t;
    return *this;
  }

 protected:
  std::ostringstream code;

 private:
  template <typename T, typename... REST>
  void GenCode(T t, REST... args) {
    GenCode(t);
    code << ", ";
    GenCode(args...);
  }

  // general type
  template <typename T>
  void GenCode(T t) {
    code << t;
  }

  // Convert pointer to string
  template <typename T>
  void GenCode(T *t) {
    if (t == nullptr) {
      code << "NULL";
    } else {
      std::string name = MemoryAllocator::GetInstance()->GetRuntimeAddr(t, true);
      if (name.empty()) { exit(1); }
      code << name;
    }
  }

  // std::boolalpha converts bool to string literals {"true", "false"} instead of {1, 0}
  void GenCode(bool t) { code << std::boolalpha << t; }
  void GenCode(int8_t t) { code << std::to_string(t); }
  void GenCode(uint8_t t) { code << std::to_string(t); }
  void GenCode(decltype(nullptr) t) { code << "NULL"; }
  void GenCode(const char *t) { code << t; }
  void GenCode(LiteDataType t) { code << "(LiteDataType)" << t; }
};

上段程式碼以跳入某個特化的版本為遞迴終止條件。
除了給出特化版本,也可以使用 type_traits + tuple 判斷size展開引數包。

  1. 逗號表示式+初始化列表 展開函式包
template<class T>
void printArg(T t) {
  std::cout << t << std::endl;
}

template<class ...Args>
void print(Args... args) {
  std::initializer_list<int>{(printArg(args), 0)...}; // 也可使用 lambda 
  // 等價於建立了一個元素均為0的陣列
  // 但每個元素初始化前,由於存在逗號表示式,會先呼叫printArg,再返回0
}
  1. c++17特性
  • Unary right fold (E op ...) becomes (E1 op (... op (EN-1 op EN)))
  • Unary left fold (... op E) becomes (((E1 op E2) op ...) op EN)
  • Binary right fold (E op ... op I) becomes (E1 op (... op (EN−1 op (EN op I))))
  • Binary left fold (I op ... op E) becomes ((((I op E1) op E2) op ...) op EN)

3.2.2 可變引數模板類

// 好用?

3.2.3 可變引數模板消除重複程式碼

例1,如 3.2.1 中的示例
例2,某開原始碼中,由不同建構函式 建立單例物件的工廠類

template <typename T>
class Singleton {
 public:
  explicit Singleton(T &&) = delete;
  explicit Singleton(const T &) = delete;
  void operator=(const T &) = delete;
  // thread safety implement
  template <typename... _Args>
  static T &Instance(_Args&&... args) {
    static T instance(std::forward<_Args>(args)...); // 完美轉發消除損耗
    return instance;
  }

 protected:
  Singleton() = default;
  virtual ~Singleton() = default;
};