《Effective Modern C++》学习记录(一)
类型推导
- 类型推导,主要是使用auto和decltype,更有可能只是前者(用的多)。Scott Meyers(作者)是提倡更多的使用auto,不使用具体的型别。并举例了使用auto的好处:
- auto和decltype能使用的语境越来越多,不必再去写那些不言自明或者是完全冗余的型别。比如很长嵌套数据结构里面的迭代器。
- 更高的适用性,如果在源码对一个型别改动,auto能自动适应,不用修改应用部分的代码。
- 会强制要求变量初始化,不会有int i,而i忘了初始化的问题。
(作者在后面还举了不少例子来说明好处)当然缺点是可能会导致写出来的代码比较难懂,编译器推导出来的类型,有时候不符合我们的想象。不过我认为可以辅助变量的命名,比如匈牙利命名等规范命名可以有效降低负面影响。之前有人说模版类型推导是否会影响程序的性能,并不会,内联编译,只是编译时使用,可能会导致代码体积膨胀,性能并不会受到影响。
- 函数模版类型的推导过程分两步:
template<typename T>
void f(const T& param); // ParamType是const T&
int x = 27;
const int& rx = x;
f(rx); // 这里的T是const int不是const int&,param的类型是const int&
f(x); // 这里的T是int,param是int&
上面的这段代码,T和param是分开来推导的,在形参传入的时候,T的类型是忽略引用性的,也就是说对参数是T&传入const是安全的,不会发生改变。这一点在param处为按值类传递时,会进一步忽略param处的const或volatile。
3. 万能引用
也就是如下这种情况
template<typename T>
void f(T&& param);
int x = 27;
f(x); // x是左值,那么T为int&,param为int&
f(27); // 27是右值,那么T为int,param为int&&
parm在这里是一个万能引用,很像右值引用,我简单理解就是能处理传入的实参为左值或右值的万能引用。当传入的是右值时,行为等同T&,当传入的为左值时,会有特殊处理。
auto使用
- auto的类型推导,除了个别情况,等同于上面的模版推导,两者之间存在双向的算法变换(我不会),或者说概念性等价。这里的auto相当于取代了模版中的ParamType,但是如果每次用auto的时候带入模版去思考一下,我感觉就很麻烦。
以下的例子很能说明关系:
auto x = 27;
const auto cx = x;
const auto& rx = x;
auto&& uref1 = x; // x是左值,int;那么按照T&处理,uref1是int&
auto&& uref2 = cx; // cx也是左值,const int;那么uref2是const int&
auto&& uref3 = 27; // 27是右值,所以这里的uref3是一个万能引用,是int&&
不要以为带了&&就是万能引用,要看实际的赋值,实参部分是右值还是左值。
2. auto类型推导的一个例外跟初始化列表std::initializer_list有关。
// 有以下四种初始化方式
auto x1 = 27;
auto x1(27);
auto x1 = {27};
auto x1{27};
其中,后两者的类型属于std::initializer_list,有类型收窄,也就是说auto x5 = {1,2,3.0};是通不过编译的,因为这会导致进行两次模版推导。这就很遗憾了,因为这会导致如果想用{}来统一C++中的初始化变量,会出现这个问题,导致有瑕疵。但是我还是认为这一点,记住就行,还是勉强可以认为{}统一了初始化。
而且如果直接传这个类型,模版会报错。
auto x = {1,2,3};
template<typename T>
void f(T param); // 如果说是std::initializer_list<T> param就不会报错
f({1,2,3}); // 这样是会报错的
其余细节
- 如果用的多,比较熟练,以上才能学习的融会贯通,不然前期就需要停下来慢慢分析。如下:
const int theAnswer = 42;
auto x = theAnswer;
auto y = &theAnswer;
x是int,y是const int*。这是因为auto相当于模版推导的第二阶段,而如果param只是值传递,会忽视const。而&theAnswer是一个常量的地址,属于右值,而这个地址不能修改,所以是const int*。
对象初始化
C++11引入了{}大括号初始化,从概念上统一了初始化。值得一提的是,这种初始化把可能看上去像函数的变量初始化给干掉了,C++里面的声明只要能够被解析到,就认为是声明。Button pBtn(xxx)
就不会出现是变量声明还是调用的问题了。类似的还有Button btn()
,这到底是初始化还是一个函数的声明。
所以在设计类的时候,可以考虑加一个参数为初始列的构造函数。但是auto和初始列一起使用还是会出现上面提到的问题。这个东西有点坑,初始列会强烈的请求使用调用,即使类型不匹配,能隐式强制转换的,也会进行隐式转换。让我想到了突然兴奋的患者.jpg。