前言

之前分享过Scott Meyers的两本书《Effective C++》和《More Effective C++》。这两本书对我深刻学习C++有着很大的帮忙,倡议所有想进阶C++技术的同学都能够看看。然而,这两本书是大神Scott在C++11之前出的,而C++11对于C++社区来说是一次重大的改革,被称为古代C++,用以辨别C++11之前的传统C++。

好在Scott在之后也带来了全新的《Effective Modern C++》,其中也是连续了作者一贯的格调和品质。带来了42个独家技巧,助你改善C++11和C++14的高效用法(封面语)。

本文首先就带同学们一起看看这本书的前两章——类型推导和auto。

首先申明本文只是做知识点的总结,书中有更具体的推导和解说过程,感兴趣的同学还是强烈建议大家去读原书。

类型推导

条款1:了解模板类型推导

模板类型推导是C++11利用最宽泛的个性之一——auto的根底。所以,了解auto的推导规定和正确应用形式的根底就是了解模板类型推导的规定。

先来看看模板和其调用的个别模式。

template<typename T>void f(ParamType param);f(expr);

这里须要T的类型推导后果,依赖于expr的类型和ParamType的模式。其中,ParamType的模式须要分三种状况探讨。

  • 状况1:ParamType是个指针或者援用,但不是万能援用(模式如T&&)。

在这种状况下,模板类型推导具备以下规定:

  1. expr的援用属性会被疏忽。
  2. 疏忽expr的援用性后,expr的类型和ParamType的类型进行模式匹配,由此决定T的类型。

举个例子:

// 申明模板template<typename T>void f(T& param);// 申明变量int a = 1;const int ca = a;const int& cra = a;//调用模板f(a);   //a的类型是int,T的类型是int,param的类型是int&。f(ca);  //ca的类型是const int,T的类型是const int,param的类型是const int&。f(cra); //cra的类型是const int&,T的类型是const int,param的类型是const int&。
要点1:在模板类型推导过程中,具备援用类型的实参会被当成非援用类型来解决。
  • 状况2:ParamType是个万能援用。

在这种状况下,模板类型推导规定如下:

  1. 如果expr是个左值,则T和ParamType都会被推到为左值援用。
  2. 如果expr是个右值,则和状况1中的推导规定雷同。

举个同状况1相似的例子:

// 申明模板template<typename T>void f(T&& param);// 申明变量int a = 1;const int ca = a;const int& cra = a;//调用模板f(a);   //a是左值,类型是int,T的类型是int&,param的类型是int&。f(ca);  //ca是左值,类型是const int,T的类型是const int&,param的类型是const int&。f(cra); //cra是左值,类型是const int&,T的类型是const int&,param的类型是const int&。f(1);   //1是右值,类型是int,T的类型是int,param的类型是int&&。
要点2:对万能援用形参进行推导时,左值实参会进行非凡解决。
  • 状况3:ParamType即不是指针也不是援用。

这种状况就是按值传递,其指标推导规定如下:

  1. expr的援用属性会被疏忽。
  2. 疏忽expr的援用性后,如果expr还具备const或volatile属性,也会被疏忽。

还是看一下例子:

// 申明模板template<typename T>void f(T param);// 申明变量int a = 1;const int ca = a;const int& cra = a;//调用模板f(a);   //a的类型是int,T和param的类型都是int。f(ca);  //ca的类型是const int,T和param的类型都是int。f(cra); //cra的类型是const int&,T和param的类型都是int。
要点3:对按值传递的形参进行推导时,实参中的const或volatile属性,也会被疏忽。
  • 有一个非凡状况须要留神的就是数组或函数做模板的实参的状况。
要点4:数组或函数类型的实参在模板推导过程中会进化为对应的指针。除非形参param是按援用传递的,这时就会被推导为数组或函数的援用。

条款2:了解auto类型推导

如果你曾经熟练掌握了后面模板类型推导的规定,那么祝贺你也根本把握了auto类型的推导了。因为除了一个非凡状况外,auto类型推导和模板类型推导的规定是一样的。

先看和模板类型推导一样规定的示例:

auto a = 1;         //a的类型是intconst auto ca = a;  //ca的类型是const intconst auto& cra = a;//cra的类型是const int&auto&& ra1 = a;     //ra1的类型是int&auto&& ra2 = ca;    //ra2的类型是const int&auto&& ra3 = 1;     //ra3的类型是int&&

惟一的非凡状况就是在应用了C++11引入的对立初始化——大括号初始化表达式时。如果向模板传入一个大括号初始化表达式,则无奈编译通过。而auto会将其推导为std::initializer\_list。

举例如下:

// 申明模板template<typename T>void f(T param);f({1, 2, 3}); //无奈编译通过auto a = {1, 2, 3}; //a的类型是std::initializer_list<int>

另外,还有一个要留神的点是:在C++14中能够在函数的返回值或lambda表达式的形参中应用auto,但这里的auto应用的是模板类型推导,而不是auto类型推导。所以如果在这种状况下应用大括号初始化表达式也是无奈编译通过的。

条款3:了解decltype

要点1:在绝大多数状况下,decltype会返回变量或表达式确切的类型。

在C++11中,decltype的主要用途就在于申明那些返回值类型依赖于形参类型的函数模板。举个例子,咱们写一个模板函数f,其形参是一个反对方括号下标语法(即“[]”)的容器和一个下标,并在返回下标操作后果前进行合法性验证。函数的返回值类型须要与下标操作后果的返回值类型雷同。其最终实现如下:

//C++11版template<typename Container, typename Index>auto f(Container&& c, Index i) -> decltype(std::forward<Container>(c)[i]) {    checkInvalid();// 合法性验证    return std::forward<Container>(c)[i];}//C++14版template<typename Container, typename Index>decltype(auto) f(Container&& c, Index i) {    checkInvalid();// 合法性验证    return std::forward<Container>(c)[i];}//应用auto str = f(makeStringDeque(), 5);//其中makeStringDeque是一个返回std::deque<std::string>的工厂函数

条款4:把握查看类型推导后果的办法

要点1:利用IDE编辑器、编译器错误信息和Boost.TypeIndex库经常可能查看到推导失去的类型。

要点2:有些工具得出的后果可能无用或者不精确。所以,了解C++类型推导规定是必要的。

auto

条款5:优先选用auto,而非显式类型申明

C++11新增的auto,最大的作用就是让咱们远离了那些啰嗦的显示类型申明。比方我用std::function定义一个如下的函数:

std::function<bool(const std::unique_ptr<Widget>&,                     const std::unique_ptr<Widget>&)>     derefUPLess = [](const std::unique_ptr<Widget>& p1,                     const std::unique_ptr<Widget>& p2)                    { return *p1 < * p2; };

能够看到这个定义写起来真是啰嗦,且还容易一不小心写错。如果咱们用auto来定义则能够写成:

auto derefUPLess = [](const std::unique_ptr<Widget>& p1,                     const std::unique_ptr<Widget>& p2)                    { return *p1 < * p2; };

更有甚者,在C++14中auto能够应用在lambda表达式的形参中,于是咱们能够失去一个能够利用于任何相似指针之物指涉到的值的比拟函数,如下:

auto derefLess = [](const auto& p1,                     const auto& p2)                    { return *p1 < * p2; };

而且,通常状况下,std::function对象个别都会比应用auto生命的变量应用更多的内存。所以,在能应用auto的状况下,咱们通常都应该抉择应用auto。

要点1:auto变量必须初始化,基本上对会导致兼容性和效率问题的类型不匹配景象免疫,还能够简化重构流程,通常也比显式指定类型要少打一些字。

条款6:当auto推导的类型不符合要求时,应用带显式类型的初始化物习惯用法

在条款2中,咱们曾经见识了auto推导的一个非凡状况。同样,还存在着一个广泛的法则是,“隐形”代理类和auto无奈和平共处。

咱们须要先认识一下“隐形”代理类。所谓代理类,就是指为了模仿或加强其余类型的类。代理类在C++中很常见,比方规范库中的智能指针就是将资源管理嫁接到裸指针上。举个例子,通常咱们会认为std::vector<T>的operator[]会返回T&。但实际上,std::vector<bool>的operator[]的返回值类型是std::vector<bool>::reference,它就是一个“隐形”的代理类。请看以下代码:

std::vector<bool> features(const Widget& w);// 返回的vector种每一个bool值都代表着Widget是否提供一个特定的性能Widget w;auto highPriority = feature(w)[1];//第一个feature示意这个Widget是否具备高优先级processWidget(w, highPriority);//未定义行为!!!

为什么最初一句是一个未定义行为呢?因为这里auto推导的类型是std::vector<bool>::reference。而这个代理类可能会导致highPriority含有一个空悬的指针(具体起因设计标准库的实现,细节请看书)。

要点1:“隐形”的代理类型能够导致auto依据初始化表达式推导出“谬误的”类型。

解决这个问题的办法就是强制进行一次类型转换。咱们将下面出问题的语句替换为以下语句,就能够解决这个未定义行为:

auto highPriority = static_cast<bool>(feature(w)[1]);
要点2:auto推导出“谬误的”类型时能够进行强制类型转换,让auto强制推导出你想要的类型。