用std::string或std::vector<char>来保存原始数据。

9
我希望这个问题适用于stackoverflow...在存储原始数据字节(8位)时,使用 std::string 和使用 std::vector<char> 有什么区别。我从文件中读取二进制数据,并将这些原始字节存储在 std::string 中。这很有效,没有问题或异常。我的程序按预期工作。然而,其他程序员更喜欢使用 std::vector<char> 并建议我停止使用 std::string,因为它对于原始字节不安全。那么,为什么使用 std::string 来保存原始数据字节是不安全的呢?我知道 std::string 最常用于存储 ASCII 文本,但一个字节就是一个字节,所以我不明白为什么会优先选择 std::vector<char>。谢谢任何建议!

3
在C++11之前,std::string不保证提供连续的存储空间,如果你需要通过&s[0]获取一个非const指向数据的指针,这很重要。但是现在已经不再是这样了。 - Pavel Minaev
1
可能是vector <unsigned char> vs string for binary data的重复问题。 - Brian Neal
4个回答

16
The problem is not whether it works or not. The problem is that it can be very confusing for the next person who reads your code. std::string is intended for displaying text, and anyone reading your code will expect that. You can express your intention more clearly by using a std::vector<char>. This will improve your WTF/min during code reviews.

2
我从未这样想过。说得好。我认为std::string是一个容器,它包含字节。它们可能是ASCII字节,但不一定是。在我看来,std::string就是std::bytes,但知道其他人有不同的看法也是好的。我可以理解这会让人感到困惑。 - 01100110
4
更多的观点来说,当我想到“byte”时,我会想到uint8_t。大部分情况下,我只在实际持有字符数据时使用char,例如在使用缓冲区时(例如使用new char[]来分配内存以构建对象),或是与一些使用char的已有API兼容。通常我更喜欢使用std::vector<uint8_t>来存储原始字节数据。 - user1084944
@Hurkyl uint8_t 不保证长度为一个字节。而 char 则是。 - emlai
@zenith:了解导致它起作用的漏洞将是有趣的;我知道CHAR_BIT至少必须为8,对象的大小必须是字节的倍数。uint8_t不存在的路径是清晰的;你如何安排它存在并且sizeof(uint8_t)>1 - user1084944

6
在C++03中,使用std::string存储字节数据数组不是一个好主意。按照标准,std::string不必须存储连续的数据。C++11对此作出了修正,使得它的数据必须是连续的。
因此,在C++03中这样做将没有效果,除非你已经审查了你的C++标准库实现,以确保它是连续的。
无论如何,我建议使用vector<char>。通常,当你看到string时,你会认为它是一个...字符串。你知道,一种编码形式的字符序列。而vector<char>表示它不是一个字符串,而是一个字节数组。

谢谢。我们使用C++11,所以字节是连续的。我的std::string的想法似乎比大多数人更广泛。我很感激你的意见。了解为什么其他人会觉得这很困惑对我很有好处,尽管它确实有效。 - 01100110

3
除了连续存储和代码清晰度问题外,我在尝试使用std::string来保存原始字节时遇到了一些相当阴险的错误。
其中大部分集中在尝试将一个字节的char数组转换为std::string与C库进行交互时。例如:
std::string password = "pass\0word";
std::cout << password.length() << std::endl; // prints 4, not 9

也许您可以通过指定长度来解决这个问题:
std::string password("pass\0word", 0, 9);
std::cout << password.length() << std::endl; // nope! still 4!

这可能是因为构造函数期望收到一个C字符串,而不是一个字节数组。可能有更好的方法,但我最终得到了这个:

std::string password("pass0word", 0, 9);
password[4] = '\0';
std::cout << password.length() << std::endl; // hurray! 9!

有些繁琐。幸运的是,我在单元测试中发现了这个问题,但如果我的测试向量没有空字节,我会错过它。这种方法的隐蔽之处在于上面的第二种方法将正常工作,直到数组包含空字节。

到目前为止,std::vector<uint8_t>看起来是一个不错的选择(感谢J.N.和Hurkyl):

char p[] = "pass\0word";
std::vector<uint8_t> password(p, p, p+9); // :)

注意:我没有尝试使用std::string的迭代器构造函数,但这种错误非常容易出现,因此甚至可能值得避免出现这种可能性。
得到的教训:
  • 使用包含空字节的测试向量测试字节处理方法。
  • 在使用std::string保存原始字节时要小心(我建议避免使用)。

在C++11中,你能否从字符串字面量初始化一个charuint8_t的向量? - M.M

0
使用std::string来存储文本文件中的字符。这样你就可以放心,字符编码会被正确处理。
使用std::vector<std::byte>来存储二进制文件中的原始字节。这将防止意外的类型转换,并确保数据操作的安全性。数据的精确表示非常重要,意图也得到了传达。

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