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() 参数任意,会寻找对应参数的构造函数(both 隐式/显式),当场构建元素到容器中,省去了拷贝或移动元素的过程。

在不能包含重复元素的set或map中,即使对应的key有元素,emplace_back() 依然会先构造元素,再去销毁。可能产生额外开销。
特殊例子:局部vector<unique_ptr>传入栈变量指针,在vector生命期结束造成这个栈变量执行delete析构,导致UB:链接

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

这个工厂模式示例也不错。

3.3 可变参数模板和 type_taits 的综合应用

(c++11 实现optional、lazy、dll帮助类、lambda链式调用、any ...)[https://github.com/qicosmos/cosmos/tree/master/cpp11book/第三章]

posted @ 2022-02-20 15:29  HarryPotterIsDead!  阅读(130)  评论(0编辑  收藏  举报