为什么string::data()不提供可变的char*?

25
arraystringvector 中都有 data 方法,它们的作用是:

返回指向底层数组的指针,该数组用作元素存储。即使容器为空,范围 [data(); data() + size()) 也始终是有效范围。[Source]

对于所有适用的容器,该方法提供了可变和 const 版本,例如:
T* vector<T>::data();
const T* vector<T>::data() const;

所有适用的容器,即除了 string ,它只提供了const版本

const char* string::data() const;

这里发生了什么?为什么string被短缺了,当char* string::data()会非常有帮助时?

10
已经讨论过并记录为一个问题,详情请点击这里 - Alper
1
@black 我广泛使用了 vector<char>,但每次在将字符复制回 string 时都感到后悔。实际上我想要的是一个带有可修改缓冲区的 string - Jonathan Mee
@ChaoSXDemon 你在使用C风格的转换,而在C++中,它简化为(并且最好写成)const_cast,其中:“通过非const访问路径修改const对象,并通过非volatile glvalue引用volatile对象会导致未定义行为。” 因此,我认为我们可以说,“不,这绝对不是一个好的做法。” - Jonathan Mee
@JonathanMee,我完全同意你的观点。令人担忧的是,我曾经在一个代码库中看到过这种情况,“未定义行为”被非常明确定义了 :( - ChaoSXDemon
显示剩余10条评论
2个回答

19
简短的回答是,确实提供了char* string::data()方法。这对于类似data函数同样至关重要,因此我现在可以这样得到对基础C字符串的可变访问:
auto foo = "lorem ipsum"s;

for(auto i = data(foo); *i != '\0'; ++i) ++(*i);

出于历史目的,值得记录一下string的发展,正在此基础上进行开发:在中,通过一个新的要求,即其元素被连续存储,使得可以访问string的底层缓冲区,以便对于任何给定的string s

&*(s.begin() + n) == &*s.begin() + n 对于任意n在[0, s.size())范围内成立,或者等价地,可以将指向s[0]的指针传递给期望指向CharT[]数组的第一个元素的函数。

可通过多种方法获得对这个新要求的底层C字符串的可变访问,例如:&s.front()&s[0]&*s.first()。但回到最初的问题,如何避免使用这些选项的负担:为什么没有以char* string::data()的形式提供对string底层缓冲区的访问权限? 要回答这个问题,需要注意T* array<T>::data()T* vector<T>::data()所需的额外添加。对于其他连续容器,如deque没有增加任何额外要求。实际上,对于string并没有额外的要求,事实上,string was contiguous的要求是新添加的。在此之前,const char* string::data()已经存在。虽然它明确不保证指向任何底层缓冲区,但它是从string中获取const char*的唯一方法。
返回的数组不需要以空字符结尾。这意味着在过渡到data访问器时,string并未被“缩水”,而是没有被包含进去,因此只有constdata访问器保留了string以前拥有的功能。在C++11的实现中,有一些自然发生的例子需要直接写入string的底层缓冲区。naturally occurring examples in C++11's implementation

18

我认为这个限制源于(2011年之前)std::basic_string不需要将其内部缓冲区存储为连续的字节数组。

然而所有其他的容器(如std::vector等)都必须按照2003标准将它们的元素存储为连续的序列;因此,data可以轻松返回可变的T*,因为没有迭代等问题。

如果std::basic_string返回一个可变的char*,那就意味着你可以将该char*视为有效的C字符串并执行C字符串操作,如strcpy,但若字符串未被连续分配,则会很容易地导致未定义行为。

C++11标准增加了规定,basic_string必须实现为连续的字节数组。不用说,您可以通过使用旧技巧&str[0]绕过此问题。


2
我认为,提供一个可变的data()方法将破坏string对象的封装性,因为这会鼓励开发人员将其用作字节缓冲区。这将是一种倒退的步骤。 - Richard Hodges
2
@JonathanMee 我相信委员会在很多问题上会和我持不同意见。在许多情况下,我相信这是件好事:-) 我想至少data()方法明确地表达了意图,而&s[0]则有些晦涩难懂。所以好吧,我被说服了。 - Richard Hodges
1
@JonathanMee 如果由于缺少适当的操作符而缺少ostream功能,那么修复标准以提供一个是否更好?在我的看来,将格式化数据发射到流中可以通过更封装的方式实现,而不是在主线代码中使用sprintf这个地雷。作为自定义实用函数的实现细节,这更容易被原谅,我个人认为。 - Richard Hodges
1
@RichardHodges 我同意这种情绪。修复ostream的真正问题,而不仅仅是掩盖它。但这并不能阻止我想要char* string::data() :D - Jonathan Mee
3
@JonathanMee,总体而言,我同意你的观点。提供一个可变的data()函数比使用&s[0]addressof(*begin(s))来操纵它要更加清晰易懂。我认为我们刚刚创造了历史。我在公共互联网上改变了我的观点 :-)) - Richard Hodges
显示剩余9条评论

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