C++ vector<bool> 的巨坑与range_based_for

C++ vector<bool> 的巨坑与range_based_for


最近写代码的时候写了下面这样的代码:

#include <vector>
using ::std::vector;
int main()
{
	vector<vector<bool>> v(10, vector<bool>(10, false));
	for (auto& vx : v)
		for (auto& velem : vx)
		{
			velem = true;
		}
	return 0;
}

这段看似平常的代码居然会编译错误,但是如果把bool换成int,那么编译就会非常正确:

#include <vector>
using ::std::vector;
int main()
{
	vector<vector<int>> v(10, vector<int>(10, 0));
	for (auto& vx : v)
		for (auto& velem : vx)
		{
			velem = 1;
		}
	return 0;
}

搜索C++ reference1,在里面我们可以看到vector<bool>原来是vector模板类的一个偏特化,而并不是由vector直接实例化得到的。C++ reference给我们的解释是,为了便于把bool优化成比特存储,而不需要一个字节来存储。为了探寻range_based_for失败的原因,我们查阅C++标准2vector<bool> 的定义(略去了不必要的代码):

namespace std {
	template <class Allocator>
	class vector<bool, Allocator> {
	public:
		// types:
		using value_type = bool;
		using size_type = implementation-defined; // see 26.2
		using iterator = implementation-defined; // see 26.2
		using const_iterator = implementation-defined; // see 26.2
		// bit reference:
		class reference {
			friend class vector;
			reference& operator=(const bool x) noexcept;
			/*some code*/
		};
	
		iterator begin() noexcept;
		iterator end() noexcept;
	
		// element access:
		reference operator[](size_type n) ;
		reference at(size_type n) ;
		reference front();
	};
	/*some code*/
}

结果非常的amazing啊!我们对vector<bool>的每个元素进行访问的时候,实际上访问的都是vector<bool>::reference这个类,而不是其成员本身。毕竟如果编译器想要将其每个元素优化成二进制位的话,C++语言由于不存在直接访问二进制位的操作,就无法直接访问其每个元素,所以统一为访问这个类来对每个元素进行引用。了解了这些,我们想解答编译出错的事情,还需要知道range_based_for的实现。C++标准2中如此规定range_based_for:

for ( for-range-declaration : for-range-initializer ) statement
/*is equivalent to*/
{
	auto &&__range = for-range-initializer;
	auto __begin = begin-expr;
	auto __end = end-expr;
	for (; __begin != __end; ++__begin) {
		for-range-declaration = *__begin;
		statement
	}
}

那么,我们对一维情况vector<bool> v; for (auto& velem : v) {}进行等价展开,它就等价于:

vector<bool> v;
{
	auto &&__range = v;	//auto被推导为vector<bool>&,因此__range为auto&&&,即auto&
	auto __begin = __range.begin();	//auto被推导为vector<bool>::ierator
	auto __end = __range.end();	//同上
	for (; __begin != __end; ++__begin) {
		auto& velem = __begin.operator*();		//error! velem被推导为vector<bool>::reference&
		statement
	}
}

显然标记了//error!的那一行就产生了错误!因为__begin.operator*()是一个函数调用,而函数的返回值是一个右值,即vector<bool>::reference的临时对象,而我们却给它赋值给了左值引用vector<bool>::reference&,因此会报出编译错误。

那么,我们如果要修改v的元素该怎么办呢?这是一个令人发指的操作:注意到我们的vector<bool>::reference是对vector<bool>的一个元素的引用(可以理解为指针),那么我们只需要值传递vector<bool>::reference即可,不需要进行引用传递!即代码如下:

::std::vector<bool> v(10, false);
for (auto velem : v)
{
	velem = true;
}
::std::cout << (int)v[5] << ::std::endl;

输出的结果果然是1!这个操作与其他类型的vector完全不同。因为我们知道,把vector<bool>换成vector<int>的话:

::std::vector<int> v(10, 0);
for (auto velem : v)
{
	velem = 1;
}
::std::cout << (int)v[5] << ::std::endl;

它的输出结果显然仍然是0,因为值传递不1能改变v内元素的值,而vector<bool>与我们的习惯完全不同!这是需要特别注意的。

我们回到最开始的问题,我起初报错的代码就可以改成:

#include <vector>
using ::std::vector;
int main()
{
	vector<vector<bool>> v(10, vector<bool>(10, false));
	for (auto& vx : v)
		for (auto velem : vx)	//此处为auto,并非auto&!
		{
			velem = true;
		}
	return 0;
}

这就是vector<bool>与普通的vector的不同之处。
因此,C++ reference中也警告我们,我们无法使用无特化的bool类型。如果这种特化的bool类型不能满足我们的需求,我们只能用charunsigned char或其他的封装类型来代替,或者使用其他的容器如deque来代替。

参考文献


  1. C++ reference: http://www.cplusplus.com/reference/vector/vector-bool/ ↩︎ ↩︎

  2. 《ISO/IEC 14882: 2017 Programming languages — C++》 ↩︎ ↩︎

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值