条款6:当auto推导出非预期类型时应当使用显式的类型初始化
1.一种特殊情况:auto
在推导vector<bool>
时会返回vector<bool> reference
- 常识是:
std::vector<T>
的operator[]
常常返回一个T&
std::vector<bool> features();
auto ret = features(); //返回vector<bool> reference
bool ret2 = features(); //返回bool(通过隐式转换)
auto ret3 = static_cast<bool>(features); //返回bool(通过static_cast强制casting)
-
vector<bool>
的operator[]
的返回值其实并不是bool
类型,vector<bool>
比较特殊,它返回的是vector<bool>::reference
,为什么要这样呢?返回一个bool引用类型不就完了嘛,标准库干嘛非要这么大费周章的搞了个这样的类型呢?,原因有以下几个:- 因为
bool
占用一个字节,标准库为了节省内存,改用bit来表示; - 因为
operator[]
需要返回一个内部元素的引用,但是没办法对一个bit进行引用; - 为了让返回的类型统一,无论是
bool
类型,还是其它类型;
- 因为
-
为此标准库为了实现上述三个目标就封装了一个内部的类型
vector<bool>::reference
,是一个proxy代理类,具体参见cppreference因此auto在这里老老实实得到了一个vector<bool>::reference
类型。 -
我们可以通过使用
static_cast
强制进行类型转换得到我们想要的类型,以避免上述问题
2.个人思考:
- 如果我都使用强制转换类型了,那还有什么必要使用
auto
呢?使用auto
的目的本来就是想要让编译器帮助我们进行类型推断,以减少重复的类型名使用,那么这样做的意义在哪? 仅仅是为了解决在使用auto
时出现的问题吗?个人的一个思路是——auto
常常用于泛型编程中实现自动推断类型,这样在整个模板在有一处发生变动时由于有auto
的存在可以不用改动太多地方就能继续适用。同时,对于一些基础的内置类型,也可以使用auto
(基本不会犯错)。
3.请记住:
- 对于一些看不见的proxy类型,使用auto对这类初始化表达式进行类型推导会推导出错误的类型。
- 通过显示的类型初始化惯用法可以强制auto推导出目标类型。
第三章:使用Modern C++(C++ 2.0及以上)
条款7:在创建对象时区分()
和{}
1.一般来说,值得初始化有如下三种方式:
int x(0); // initializer is in parentheses
int y = 0; // initializer follows "="
int z{
0}; // initializer is in braces
int c = {
0}; // initializer uses "=" and braces
std::vector<int> v1(10,20); //使用的是非初始化列表的版本,10个元素,每个元素的值是20
std::vector<int> v2{
10,20}; //使用的带初始化列表的版本,2个元素,值分别是10,20
2.等号往往是最容易误导人判断的符号,必须区分赋值与初始化的关系
Widget w1(2); //调用的默认构造函数
Widget w2 = w1; //调用的是拷贝构造函数
w1 = w2; //调用的赋值操作符
3.特例:在C++11中引入的std::atomic
是一个不可拷贝的对象,对于它的初始化是不能利用 y = 0
这种形式的,因为它会调用默认拷贝构造函数。
std::atomic<int> ail{
0}; //fine
std::atomic<int> ai3 = 0; //error
- 因此我们得到结论:
y = 0
和x(0)
这两种形式都有其不适用的地方,而z{0}
这种形式则都可以适用,这也就是为什么在C++11中这种初始化方式被称为统一初始化的原因。除此之外统一初始化的这种方式还可以避免窄化的转换和复杂的语法分析。
4.()
与{}
二者最大的区别是:{}
这种初始化方式调用的是带有initializer_list
的构造函数
class Widget {