概述
条款6:当auto推导出非预期类型时应当使用显式的类型初始化
std::vector<T>
、std::deque<T>·的
operator[]常常返回一个
T&`,std::vector<bool>
的不同
std::vector<bool>
特化定义std::vector<bool>::reference
为可公开访问的嵌套类。
std::vector<bool>::reference
代理访问std::vector<bool>
中单个位的行为。
std::vector<bool>::reference
的基础使用是提供能从operator[]
返回的左值。
任何通过std::vector<bool>::reference
发生的对vector
的读或写,会潜在地读或写整个底层的vector
。
有关vector<bool>
的更多示例详见vector 类,此处我们重点关注《条款6》
分析
代码:
#include <iostream>
#include <vector>
using namespace std;
class widget{
public:
vector<bool> bVector={false,true,true,false,true,true};
int other = 0;
};
vector<bool> get(const widget &w){
return w.bVector;
}
void process(widget w,bool boolValue){
cout<<"boolValue= "<<boolValue<<endl;
}
int main() {
widget w;
//情况1
bool v = get(w)[2];
process(w,v);
//情况2
auto v1 = get(w)[2];
process(w,v1);
//解决方法
auto v2 = static_cast<bool>(get(w)[2]);
process(w,v2);
}
编译生成的代码
#include <iostream>
#include <vector>
using namespace std;
class widget{
public:
std::vector<bool, std::allocator<bool> > bVector;
int other;
// inline widget(const widget &) noexcept(false) = default;
// inline ~widget() noexcept = default;
// inline constexpr widget() noexcept(false) = default;
};
std::vector<bool, std::allocator<bool> > get(const widget & w)
{
return std::vector<bool, std::allocator<bool> >(w.bVector);
}
void process(widget w, bool boolValue)
{
std::operator<<(std::cout, "boolValue= ").operator<<(boolValue).operator<<(std::endl);
}
int main()
{
widget w = widget();
bool v = static_cast<bool>(get(w).operator[](2).operator bool());
process(widget(w), v);
std::_Bit_reference v1 = std::_Bit_reference(get(w).operator[](2));
process(widget(w), static_cast<bool>(v1.operator bool()));
bool v2 = static_cast<bool>(get(w).operator[](2).operator bool());
process(widget(w), v2);
return 0;
}
分析
情形1
bool v = static_cast<bool>(get(w).operator[](2).operator bool());
process(widget(w), v);
调用operator[]
后返回的是std::_Bit_reference
,又调用了operator bool()
返回了bool的引用,这是没有问题的。
情形2
生成的代码如下:
std::_Bit_reference v1 = std::_Bit_reference(get(w).operator[](2));
process(widget(w), static_cast<bool>(v1.operator bool()));
可以看到的是auto
关键字推断出的是一个std::_Bit_reference
,这已经不是一个bool 引用或者bool变量了,而是一个嵌套在vector<bool>
里的一个代理类,这样编译是没有问题的,但是结果却不一样了,至于为啥要这么干,原因大致如下:
- 因为bool占用一个字节,标准库为了节省内存,改用bit来表示;
- 因为operator[]需要返回一个内部元素的引用,但是没办法对一个bit进行引用;
- 为了让返回的类型统一,无论是bool类型,还是其它类型;
注意结果不一致这句话 , 再看下情形1生成的代码
bool v = static_cast<bool>(get(w).operator[](2).operator bool());
只不过是加了std::_Bit_reference v1
这么一个中间对象,就不一样了吗OMG?随即查到了大神关于operator[]
的解析
下图原文连接
再结合汇编代码
得出了个人感想: 这个vector<bool>
在调用完释放掉了,仅剩一个引用对象std::_Bit_reference
在孤芳自赏,再去调用operator bool()
。此时应该crash
但是没有,继续分析下std::_Bit_reference
于是决定加断点分析下:
加断点,发现*_M_p
的指向0,而_M_mask
的值缺依旧在,至此觉得是vector<bool>
释放导致了该指针指向了空,再看opertaor bool()
的实现大致如下:
operator bool()const {return !!(*_M_p&_M_mask);}
至此,大致明白了,不崩也正常,但值肯定就不对了。
解决情况2->用强了
auto v2 = static_cast<bool>(get(w)[2]);
process(w,v2);
生成如下:
bool v2 = static_cast<bool>(get(w).operator[](2).operator bool());
process(widget(w), v2);
程序的运行结果
若不看结果,你可能对此条款有诸多怀疑,原谅我把结果写在最后