使用basic_string<bool>解决vector<bool>的问题?

10

这是一个安全的解决方案吗?我想使用 vector bool,但需要传递指向期望 C 风格数组的旧代码的指针。

typedef std::basic_string<bool> vector_bool;

int main()
{
    vector_bool ab;
    ab.push_back(true);
    ab.push_back(true);
    ab.push_back(true);
    ab.push_back(false);
    bool *b = &ab[0];
    b[1] = false;
}

编辑: 感谢其他解决方案的建议,但我真的很想得到关于上述解决方案的明确答案。谢谢。


2
你的旧代码是要打包位(packed bits),还是每个布尔值都在单独可寻址的位置上? - us2012
2
vector<char>怎么样? - jrok
1
而C代码有多久了?你确定C语言的_Bool类型和C++的bool类型具有相同的表示形式吗? - Jonathan Wakely
2
如果我使用vector<char>,我不能将其传递给(bool *)参数,因为它们的大小可能不同。 - Neil Kirk
3
C++委员会真的应该删除std::vector<bool>的特化,并提供一个单独的紧凑数据结构,命名为类似于std::bit_vector的东西。至少这是我的想法。std::vector<bool>破坏了很多代码。 - Cornstalks
显示剩余5条评论
3个回答

17

我对于 std::basic_string<bool> 不确定,因为它将实例化 std::char_traits<bool>,我不确定标准是否要求其被定义,或者 char_traits 主模板可以保留未定义状态,只有显式特化如 char_traits<char> 被定义。你不允许提供自己的 char_traits<bool> 特化,因为只有在特化依赖于用户定义类型时,才能专门化标准模板,而显然 bool 不是这样的类型。

话虽如此,如果你的 stdlib 确实有默认的 char_traits 定义,并且你不尝试使用需要 char_traits 成员执行任何有用操作的字符串操作,则可能会起作用。

另一种选择是变通的,但可能有效:

struct boolish { bool value; };
inline boolish make_boolish(bool b) { boolish bish = { b }; return bish; }

std::vector<boolish> b;
b.push_back( make_boolish(true) );
bool* ptr = &b.front().value;

boolish是一个简单的类型,只要boolish数组与bool数组有相同的表示形式(你需要检查你编译器是否如此,我使用了static_assert来检查没有填充),那么你可能可以轻松地通过它,尽管它可能违反了别名规则,因为*ptr*++ptr不属于同一数组,所以增加指针并不指向下一个boolish::value,而是指向前一个的“末端”(即使这两个位置实际上具有相同的地址,尽管[basic.compound]/3似乎表明++ptr确实会“指向”下一个bool)。

C++11的语法变得更容易,您不需要make_boolish...

#include <vector>
#include <assert.h>

struct boolish { bool value; };

int main()
{
  std::vector<boolish> vec(10);
  vec.push_back( boolish{true} );
  bool* ptr = &vec.front().value;
  assert( ptr[10] == true );
  ptr[3] = true;
  assert( vec[3].value == true );

  static_assert( sizeof(boolish) == sizeof(bool), "" );
  boolish test[10];
  static_assert( sizeof(test) == (sizeof(bool)*10), "" );
}

谢谢您的建议,但我有几个问题。首先,您确定我可以安全地将指向boolish的指针作为bool *,不会有隐藏的填充吗?其次,我的问题是关于我发布的特定解决方法是否有效。谢谢。 - Neil Kirk
@NeilKirk,回答已更新,附上我对 basic_string<bool> 的想法。 - Jonathan Wakely
1
能否向boolish结构体添加一些“operator bool”函数,使其更加方便? - Viktor Sehr
标准布局保证了这里的不变量,我相信。 - Puppy

2

来自“Working Draft C++,2012-11-02”

21.1 通用 [strings.general]
1 此条款描述了用于操作任何非数组POD(3.9)类型序列的组件。

21.4.1 basic_string通用要求 [string.require]
5 basic_string对象中的char-like对象必须被连续存储。也就是说,对于任何basic_string对象s,当0 <= n < s.size()时,&*(s.begin() + n) == &*s.begin() + n应成立。

但是

6 引用、指针和迭代器引用basic_string序列的元素可能会被以下使用basic_string对象的方式使其无效:
— 作为任何标准库函数的参数,该函数将non-const basic_string作为参数传递给引用。233
— 调用非const成员函数,除了operator[]、at、front、back、begin、rbegin、end和rend。

所以,只要注意在其他地方使用原始数组时不要调用这些函数,就可以安全使用。

更新:

字符特征和要求在21.2字符特征[char.traits]21.2.1字符特征要求[char.traits.require]中描述。此外,typedef和特化分别在21.2.2 traits typedefs [char.traits.typedefs]21.2.3 char_traits specializations [char.traits.specializations]中描述。

这些特征也在输入/输出库中使用。因此,有一些要求,例如eof()pos_typeoff_type,在basic_string的上下文中没有意义。

我没有看到任何对这些特征实际上由实现定义的要求,除了charchar16_tchar32_twchar_t的四个特化。

尽管,在您的示例中,它与gcc 4.7开箱即用,但我定义了一个最小的bool_traits,只需要

struct bool_traits {
    typedef bool char_type;
    static void assign(char_type &r, char_type d);
    static char_type *copy(char_type *s, const char_type *p, std::size_t n);
    static char_type *move(char_type *s, const char_type *p, std::size_t n);
};

我使用了默认提供的实现(gcc 4.7),并将其用作如下:

std::basic_string<bool, bool_traits> ab;

你的环境可能已经提供了一个可用的实现。如果没有,你可以自己实现一个简单的bool_traits或一个模板特化的std::char_traits<bool>

您可以在工作草案,PDFcppreference.com - std::char_traits中查看字符特性的完整接口。


2
据我所知,双端队列并不保证所有元素都是连续的。此外,我只关心我的解决方案是否可行。谢谢。 - Neil Kirk
1
随机访问迭代器,没错,但并非所有元素都是连续的,它将它们存储在“页面”中,因此您会得到几个连续的块,而不是单个大的连续块。因此,将其传递给期望数组的东西是行不通的。只有vectorbasic_string保证连续存储。 - Jonathan Wakely
@JonathanWakely 你说得对,在deque中没有提到连续内存,已修正。 - Olaf Dietsche
谢谢,这就差不多了!!!你对Jonathan Wakely提到的char_traits可能存在的问题有什么看法吗?我对它们一无所知。 - Neil Kirk

1
您也可以使用 boost::container::vector。它与 std::vector 完全相同,但它没有为 bool 进行专门化处理。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接