Effective Modern C++ 读书笔记之第一章

在 Thu 21 July 2016 发布于 读书笔记 分类

Item 1: 理解模板类型推导

C++98只有一种模板参数类型推导即函数模板参数类型推导,但是在C++11之后,修改了原有的类型推导规则,并加入了autodecltype的推导规则。 函数模板形如如:

template<typename T>
void f(ParamType param);// ParamType可以是T加上各种修饰,如const, reference,指针等

然后在程序逻辑中调用该函数:

f(expr); // expr为传入到函数f中的参数

在编译器进行编译的时候,编译器通过expr太进行两部分的推导:ParamTypeT。 在这里,书中总结了ParamType的三种不同类型: 1. 当ParamType是指针或者引用的类型,但不是universal reference类型(这个会在Item 24讲到)。

  1. ParamTypeuniversal reference类型

  2. ParamType既不是指针也不是引用类型。

下面依次详解ParamType在不同类型下,参数推导的机制。

Case 1: ParamType是指针或者引用类型, 但不是universal reference

对于此种情况下,推导的机制如下: 1. 如果expr的类型是个引用,忽视该引用, 同样也适用于指针。

  1. 通过对expr的类型进行ParamType的模式匹配,来确定T的参数类型

例如, 模板如:

template<typename T>
void f(T& param); // ParamType 为 T&

有如下:

int x = 27;
const int cx = x;
const int& rx = x

f(x);  // T => int , ParamType => int&
f(cx); // T => const int, ParamType => const int&
f(rx); // T => const int, ParamType => const int&

如果把模板中的T& 换成 cont T&

template<typename T>
void f(const T& param);

有如下:

f(x);  // T => int, ParamType => const int&
f(cx); // T => int, ParamType => const int&
f(rx); // T =>int,  ParamType => const int&
Case 2: ParamTypeuniversal reference类型

对于此类型的推导规则如下:

  1. 如果expr是左值, 直接推导为左值引用。
  2. 如果expr是右值, 推导规则和当ParamType是指针或者引用,但不是universal reference参数推导规则相似。

模板如:

f(x); // x 是左值, T => int&, ParamType => int&
f(cx); // cx 是左值, T => const int&, ParamType => const int&
f(rx); // rx 是左值, T => const int&, ParamType => const int&
f(27); // 27 是右值, T => int, ParamType => int&&
Case 3: ParamType 既不是指针又不是引用

模板形如:

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

对于这种类型,函数模板的类型推导规则如下:

  1. 如果expr的引用类型,忽略引用。
  2. 在忽略了引用后,如果expr还有const 或者 volatile, 也忽略。

如:

f(x);  // T => int, ParamType => int
f(cx); // T => int, ParamType => int
f(rx); // T => int, ParamType => int

因为模板中是按值传入参数, 即复制,进入函数的参数在复制后和原来的参数无关了,所以 附加在参数上的 constvolatile 已经失去作用, 对于指针也是这样。 但是对于传入的参数const char*类型的参数, 需要注意的是参数为指针不假,但是其指向的内存是不可以被修改的,所以在参数推导的时候: TParamType的推导结果都是 const char*

数组在某种意义上是指针,但是又和指针有所不同,但是在作为参数传递到函数时,数组会退化成指针。

const char name [] = "J. P. Briggs";
const char* ptrToName = names;

在上面的代码中,name的类型为const char[13],而ptrToName的数据类型为const char*,二者是不相同的。但是作为参数传递到函数,下面是等价的:

void f(const char* buf);
f(name);
//等价于
f(ptrToName)

如果有函数模版:

template<typename T>
void f(T param);
f(name);

数组作为参数传递,在这里模板函数的的参数推导可以参照Case 3,即T就是const char*, ParamType就是const char*

但是如果函数模版如:

template<typename T>
void f(T& param);
f(name);

此时,在这里通过const char[13]来给T进行类型推导,所以f的参数的类型的推导为const char (&)[13],即引用一个类型为const char[13]的数组。 可以根据这个特性,写一个通用的获取数据大小的函数模版:

template<typename T, std::size_t N>
constexpr std::size_t arraySize(T (&)[N])
{
  return N;
}

对于函数,情况和数组类似:

void target(const char* name);
template<typename T>
void f(T param);

f(target); // f的参数会被推导为 void(*)(const char* name);

///////////////
template<typename T>
void f(T& param);

f(target); // f的参数会被推导为 void (&)(const char* name);

Item 2: auto类型推导

c++11新的auto关键词可以通过变量的初始化来推断参数的数据类型,具体看Effective Modern C++ 读书笔记之第二章auto的类型推导和模版参数推导几乎一样,有着对应的关系,在Item 1中,使用的如下代码:

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

Item主要讨论auto的类型推导规则。

当一个变量是通过auto来声明的, 那auto就相当于是上面的T,声明参数名称的左边的类型说明表达式就相当于ParamType

auto x = 27;
//相当于
template<typename T>
void func_for_x(T param)
func_fo_x(27);
/////////////////////////
const auto cx = x;
//相当于
template<typename T>
void func_fo_cx(const T param)
func_for_cx(x);
//////////////////////
const auto& rx = x;
template<typename T>
void func_for_rx(const T& param)
func_for_rx(x);

所以,类似于Item 1所讲的三个模版参数类型推导的策略,auto的参数类型推导也有三个策略: 1. 类型说明是指针或者引用,但是不是universal reference 2. 类型说明是universal reference 3. 类型说明既不是引用也不是指针 这三种情况下的参数推导规则和Item 1中的三种情况下的推导规则相同,如:

auto x = 27;       // case 3 x的类型是 int
const auto cx = x; // case 3, x的类型是 int
const auto& rx = x;// case 1, x的类型是 int

auto&& uref1 = x;  // case 2 x是左值,uref1的类型是 int&
auto&& uref2 = cx; // case 2 cx是左值,uref2的类型是 int&
auto&& uref3 = 27; // case 3 27是右值,uref3的类型是 int&&
const char name[] = "R. N. Briggs";
auto arr1 = name; // arr1's type is const char*
auto& arr2 = name;// arr2's type is const char(&)[13]

void someFunc(int, double);
auto func1 = someFunc; // func1's type is void(*)(int, double)
auto& func2 = someFunc; // func2's type is void(&)(int, double)

但是有点不同的是, auto在进行如下方式初始化:

auto x3 = {27};

此时x3的类型在推导上就不同了,如果auto变量是通过aggregate initialization来进初始化的时候,此时auto变量会被推导为std::initializer_list<T>而不是int。需要记住的是,auto作为函数的返回值或者lambda表达式的参数是使用的是模板参数推导,而不是auto自己的类型推导策略。

Item 3: 理解decltype

Item 4: