Introduction
argument 是实参,可以是左值也可以是右值;
parameter 是形参,一定是左值。
basic exception safety guarantee 是指发生异常后程序的不变量仍然得到保证以及没有资源被泄露;
strong exception safety guarantee 是指发生异常后程序的状态要和调用这个抛出异常的函数之前一样。
Chapter 1 Deducing Types
- C++ 98 中类型推导只用于函数模板的类型参数,C++ 11 中新引入了两个使用类型推导的场景:
auto
和decltype
。
Item 1 Understand template type deduction
考虑这样的场景:
1 | template<typename T> |
类型推断大致分为三类:
ParamType
是指针或引用(但不是 universal reference):如果expr
是引用的话,去掉&
,然后将expr
的类型和ParamType
相比,得到T
(ParamType
包含T
)。ParamType
是 universal reference:即ParamType
是T&&
。如果expr
是左值,T
被推断为左值引用,然后根据 reference collapsing 将ParamType
也折叠成左值引用;如果expr
是右值,T
被推断为正常类型(不含&
),expr
为右值引用。ParamType
不是指针也不是引用:忽略expr
的&
,const
以及volatile
,然后和ParamType
相比得到T
。需要注意的是,对于expr
是const int *const
这种情况,只能忽略后面那个const
。
对于 expr
是数组的情况(例如 int[5]
):
- 如果
ParamType
不是引用,T
被推断为int *
(typeid 为Pi
)。 - 如果
ParamType
是引用,T
被推断为int(&)[5]
(typeid 为A5_i
)。
对于 expr
是函数的情况(例如 int(char)
):
- 如果
ParamType
不是引用,T
被推断为int (*)(char)
(typeid 为PFicE
)。 - 如果
ParamType
是引用,T
被推断为int (&)(char)
(typeid 为FicE
)。
Item 2 Understand auto type deduction
auto type deduction 和 template type deduction 类似,只有一点不同。
先说相同点,考虑这样的场景:
c++1
2
3auto x = 27;
const auto cx = x;
const auto& rx = x;定义变量时的 type specifier(该处的
auto
,const auto
,const auto&
)即相当于 template type deduction 中的ParamType
,而auto
本身则相当于T
。也就是说,
const auto& rx = x;
所做的推断和以下 template type deduction 所做的推断是一样的:c++1
2
3
4template<typename T>
void func_for_rx(const T& param);
func_for_rx(x);相同点说完了,然后是不同点:在定义变量时如果用花括号进行初始化,
auto
会被推断为 initializer_list;而对于模板函数来说,不允许对花括号括起来的一组值进行类型推断。此外,C++14 中还引入了
auto
作为函数返回值和auto
作为 lambda 表达式入参这两种机制。在这两种情形中,auto
实际使用 template type deduction 的原则进行类型推断而非 auto type deduction,即返回值和 lambda 表达式的入参不能是花括号括起来的一组值。
Item 3 Understand decltype
decltype
是一个关键字,它接受一个变量名或表达式作为参数,返回其类型。这种不做任何修改,直接返回参数类型的类型推断方式也叫 decltype type deduction。值得一提的是
decltype ((x))
这种用法(x
是变量名),推断出的类型为左值引用,因为参数是左值表达式而非变量名,但这不能算特例,只是反直觉而已。把函数的参数传递给
decltype
,可以将decltype
推断出的类型作为返回值写在函数参数列表之后,这种用法叫 trailing return type(写在参数列表之后是因为decltype
需要用到函数的参数,而这些参数在参数列表中定义):c++1
2
3
4
5
6template <typename Container, typename Index>
auto authAndAccess(Container& c, Index i) -> decltype(c[i])
{
authenticateUser();
return c[i];
}而在 C++14 中,可以省略 trailing return type,用之前提到过的 template type deduction (
auto
作为返回值的情况)来推断返回值:c++1
2
3
4
5
6template <typename Container, typename Index>
auto authAndAccess(Container& c, Index i)
{
authenticateUser();
return c[i];
}但是这就有一个问题,
c[i]
返回的一般都是左值引用T&
,而 template type deduction 中会忽略掉&
,所以函数的返回值就变成了T
,这与decltype(c[i])
不一致。为了解决这个问题,C++14 引入了decltype(auto)
,即使用 decltype type deduction 来推断返回值类型:c++1
2
3
4
5
6template<typename Container, typename Index>
decltype(auto) authAndAccess(Container& c, Index i)
{
authenticateUser();
return c[i];
}decltype(auto)
还可以像auto
那样用于定义变量,区别是前者使用 decltype type deduction,而后者使用 auto type deduction。
Item 4 Know how to view deduced types
作者提供了三种查看类型推断结果的方法:分别在开发时刻(编辑代码时),编译时刻以及运行时刻。
开发时刻:利用 IDE 提供的功能,通常将光标悬停在变量名上就能看到其类型。
编译时刻:利用未定义的模板,通过编译器的报错信息获得变量的类型:
c++1
2
3
4
5
6
7
8template<typename T>
class TD;
const int theAnswer = 42;
auto x = &theAnswer;
// error: aggregate 'TD<const int *> xType' has incomplete type and cannot be defined
TD<decltype(x)> xType;注意这里类型推断使用的原则是 auto type deduction 的 case 3(即
ParamType
不是指针也不是引用),expr
是const int *
,虽然含有const
,但是是在*
之前,所以不能忽略(即新定义的指针本身的常量性由ParamType
中是否含有const
决定,而被指资源的常量性则由expr
中是否含有const
决定)。运行时刻:利用
typeid
和type_info
的name
方法获得变量的类型:c++1
2
3
4
5
6
7
8
9
10
11
12
13template<typename T>
void f(const T& param)
{
cout << "T = " << typeid(T).name() << '\n';
cout << "param = " << typeid(param).name() << '\n';
}
std::vector<Widget> createVec();
const auto vw = createVec();
if (!vw.empty()) {
f(&vw[0]);
}输出的结果是 compiler-dependent 的,在 GNU 和 Clang 中是:
Code1
2T = PK6Widget
param = PK6Widget也就是
const Widget *
。这里可能有几处误解:
param
(即&vw[0]
)类型是const Widget *
,按照 template type deduction 的 case 1,T
应该是Widget *
才对?注意 template type deduction 的 case 1 中所说的 “将
expr
的类型和ParamType
相比,得到T
”,并不是简单的模式匹配:Code1
2expr: const Widget *
ParamType: const T &看上去
T
好像是Widget *
,但是需要考虑到涉及指针时,常量性有两重含义:指针本身和被指资源。ParamType
中的const
是说param
本身(也就是指针本身)是 const,而expr
中的const
根据它的位置可以得知其含义是被指资源是 const,两者含义并不相同。如果一定要用模式匹配的概念的话,更确切的比对应该是这样:类型 常量性 引用 expr
const Widget *
× - ParamType
T
√ & 可见
expr
有而ParamType
没有的部分就是const Widget *
,所以T
就是const Widget *
。说白了导致这个误解的根本原因是
const int
和const int *
两处的const
虽然位置相似但是语义不同。刚刚解释了
T
是const Widget *
没错,但是param
难道不应该是const Widget *const &
吗?这就涉及
type_info::name
方法的机制问题了,该方法实现上要求返回的类型好像经过了一次 pass-by-value 一样(即应用一次 template type deduction 的 case 3),所以 cvr(const
,volatile
,reference
)三个性质全部被去掉了(仍然如之前讨论的一样,这里的const
是变量本身的const
,如果变量是指针,那么被指资源的const
不在此列),const Widget *const &
也就变成了const Widget *
。