Effective Modern C++
Chap-1类型推导
Item 1 理解模板类型推导
要点
- 在模板推导过程中,具有引用类型的实参会被当成非引用类型来处理。换而言之,起引用性会被忽略,指针类型亦是如此
- 对万能引用形参进行推导时,左值实参会进行特殊处理,也就是形参对引用性会被保留
- 对按值传递的形参进行推导时,若实参类型中带有
const或volatile饰词,则对它们还是会被当作不带const或volatile饰词的类型来处理,引用性也会被忽略
- 在模板类型推导过程中,数组或函数类型的实参会退化成对应的指针,除非它们被用开初始化引用
1 2 3
| template<typename T> void f(ParamType param); f(expr);
|
情况1,ParamType是个引用或指针,但不是个万能引用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| template<typename T> void f(T& param);
int x = 27; const int cx = x; const int& rx = x;
f(x); f(cx); f(rx);
void f(const T& param);
f(x); f(cx); f(rx);
template<typename T> void f(T* param);
const int *px = &x;
f(&x); f(px);
|
上述代码中T未被推导为引用或者指针,因为其引用性/指针性在推导过程中被忽略 – 要点1
情况2,ParamType是个万能引用
1 2 3 4 5 6 7
| template<typename T> void f(T&& param);
f(x); f(cx); f(rx); f(27);
|
上述代码中T得引用性被保留 – 要点2
情况3,ParamType既非指针也非引用
传值
1 2 3 4 5 6 7 8 9
| template<typename T> void f(T param);
f(x); f(cx); f(rx);
const char* const ptr = “Fun with pointers” f(ptr);
|
上述代码中传值方式的param是一个副本,忽略其const和引用性 – 要点3
数组实参
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| const char name[] = "J. P. Brigss"; const char * ptrToName = name;
template<typename T> void f(T param); f(name);
void myFunc(int param[]); void myFunc(int* param);
f(name);
template<typename T> void f(T& param);
f(name);
|
数组引用的模板类型可以被推导成数组类型,包含数组size,利用该特性可以在编译时推导出数字个数 – 要点4
以下函数被声明为constexpr,以编译时常量形式返回数组size
1 2 3 4 5 6 7
| template<typename T, std::size_t N> constexpr std::size_t arrayzSize(T (&)[N]) noexcept { return N; }
int keyVals[] = {1, 2, 3, 4, 5, 6, 7}; int mappedVals[arrayzSize(keyVals)];
|
函数实参
1 2 3 4 5 6 7 8 9 10
| void someFunc(int, double);
template<typename T> void f1(T param);
template<typename T> void f2(T& param);
f1(someFunc); f2(someFunc);
|
Item 2 理解auto类型推导
要点
- 一般情况下,auto类型推导和模板类型推导是一模一样的,但是auto类型推导会假定用大括号括起的初始化表达式代表一个
std::initializer_list,但是模板类型推导却不会
- 在函数返回值或lambda式的形参中使用auto,意思是使用模板类型推导而非auto类型推导
auto类型推导类似模板参数推导,可以参照Item 1中的推导规则,也分3类情况
- 情况 1: 类型饰词是指针或引用,但不是万能引用
- 情况 2: 类型饰词是万能引用
- 情况 3: 类型饰词既非指针也非引用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| auto x = 27; const auto cx = x; const auto& rx = x;
auto&& uref1 = x; auto&& uref2 = cx; auto&& uref3 = 27;
auto arr1 = name; auto& arr2 = name;
auto func1 = someFunc; auto& func2 = someFunc;
auto x3 = { 27 }; auto x = {11, 23, 9};
|
模板推导无法推导出{}的类型,auto类型推导可以,但是auto是返回值或lambda形参时使用模板推导
1 2 3 4 5 6 7 8 9 10 11 12
| template<typename T> void f(T param);
f({11, 23, 9});
auto createList() { return {1, 2, 3}; }
std::vector<int> v; auto resetVec = [&v](const auto& newValue) { v = newValue; }; resetVec({1, 2, 3});
|
Chap-3 转向现代C++
Item 7 在创建对象时注意区分()和{}
要点
- 大括号初始化可以应用的语境最为宽泛,可以阻止隐式窄化型转换,还对C++最令人苦恼的解析语法(most vexing parse)免疫
- 在构造函数重载决议期间,只要有任何可能,大括号初始化就会与带有std::initializer_list型别的形参相匹配,即使其他重载版本有着貌似更加匹配的形参表
- 使用小括号还是大括号,会造成结果大相径庭的一个例子是:使用两个实参来创建一个std::vector< 数值类型 >对象。
- 在模板内容进行对象创建时,到底应该使用小括号还是大括号会成为一个棘手问题。
C++11引入了统一初始化,概念上是可以用于一切场合,表达一切意思的初始化。它的基础是大括号形式,“统一初始化”是为其里,“大括号初始化”是为其表。
1 2 3 4 5 6 7 8
| int x(0); int y = 0; int z{ 0 }; Widget w1; Widget w2 = w1; w1 = w2; std::vector<int> v{1, 2, 3, 4, 5}; std::atomic<int> ai1 = 0;
|
使用大括号会进行窄化检查,避免苦恼解析,参考要点1
1 2 3 4 5 6
| double x, y, x; int sum1{x + y + z};
Widget w1(10); Widget w2(); Widget w3{};
|
大括号初始化的缺点是会伴随意外行为,要点2,3
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| class Widget { public: Widget(int i, bool b); Widget(int i, double d); };
Widget w1(10, true); Widget w2{10, true}; Widget w3(10, 5.0); Widget w4{10, 5.0};
class Widget { public: Widget(int i, bool b); Widget(int i, double d); Widget(std::initializer_list<long double> il);
operator floag() const; }; Widget w1(10, true); Widget w2{10, true};
Widget w5(w4); Widget w6{w4}; Widget w7(std::move(w4)); Widget w8{std::move(w4)};
|
Item 8 优先选用nullptr,而非0或NULL
要点
- 相对于0或NULL,优先选用nullptr。
- 避免在整型和指针类型之间重载。