为什么通过检索指向其数据的指针修改字符串是不允许的?

chr*_*ris 23 c++ string buffer language-lawyer c++11

在C++ 11中,a的字符std::string必须连续存储,如第21.4.1/5节所述:

basic_string对象中的char类对象应连续存储.也就是说,对于任何basic_string对象,标识&*(s.begin()+ n)==&*s.begin()+ n应该适用于n的所有值,使得0 <= n <s.size ().

但是,这里是§21.4.7.1如何列出两个函数来检索指向底层存储的指针(强调我的):

const charT*c_str()const noexcept;
const charT*data()const noexcept;
1返回:指针p,使得p + i ==&operator [](i)为[0,size()]中的每个i.
2复杂性:恒定时间.
3要求:程序不得更改存储在字符数组中的任何值.

我可以想到的第3点的一种可能性是指针可以通过对象的以下用途而变得无效(第21.4.1/6节):

  • 作为任何标准库函数的参数,将非const basic_string作为参数引用.
  • 调用非const成员函数,除了operator [],at,front,back,begin,rbegin,end和rend.

即便如此,迭代器也会失效,但我们仍然可以修改它们,无论它们是什么.我们仍然可以使用指针,直到它变为无效以便从缓冲区读取.

为什么我们不能直接写入这个缓冲区?是因为它会使类处于不一致状态,例如,end()不会使用新结束更新?如果是这样,为什么允许直接写入类似的缓冲区std::vector

用例包括能够将a的缓冲区传递std::string给C接口以检索字符串而不是传入一个字符串,vector<char>并使用迭代器初始化字符串:

std::string text;
text.resize(GetTextLength());
GetText(text.data());
Run Code Online (Sandbox Code Playgroud)

Nic*_*las 34

为什么我们不能直接写入这个缓冲区?

我会说明一点:因为它是const.丢弃一个const值,然后修改该数据是......粗鲁.

现在,为什么呢const?这可以追溯到写时复制被认为是个好主意的日子,因此std::basic_string不得不允许实现支持它.获取指向字符串的不可变指针(例如,传递给C-API)不会产生副本的开销将非常有用.所以c_str需要返回一个const指针.

至于为什么它仍然存在 const?嗯......这是标准中的一个古怪的东西:空终结符.

这是合法的代码:

std::string stupid;
const char *pointless = stupid.c_str();
Run Code Online (Sandbox Code Playgroud)

pointless必须是以NUL结尾的字符串.具体来说,它必须是指向NUL字符的指针.那么NUL角色来自哪里?实现有两种方法可以std::string实现:

  1. 使用小字符串优化,这是一种常见的技术.在此方案中,每个std::string实现都有一个内部缓冲区,可用于单个NUL字符.
  2. 返回一个包含NUL字符的静态内存指针.因此,如果它是一个空字符串,则每个std::string实现都将返回相同的指针.

不应该强迫每个人都实施SSO.所以标准委员会需要一种方法来保持#2在桌面上.其中一部分是给你一个const字符串c_str().而且因为这个内存可能是真实的 const,而不是假的"请不要修改这个内存const",给你一个可变的指针指向它是一个坏主意.

当然,您仍然可以通过这样做获得这样的指针&str[0],但标准非常清楚,修改NUL终结符是一个坏主意.

现在,话虽如此,修改指针及其中的字符数组是完全有效的&str[0].只要你留在半开放范围[0,str.size()).你不能通过data或返回的指针来做到这一点c_str.是的,即使标准实际上要求 str.c_str() == &str[0]是真的.

这对你来说是一个标准.

  • @chris:"允许"和"礼貌"之间存在差异.`const`对象是您和其他代码之间的契约.通过取消它,你就违反了合同.在某些条件下可能会被语言允许,但无论代码是什么,它都告诉你不要触摸它是不礼貌的.如果有人告诉你不要坐在他们的沙发上,而你这样做,他们可能不会因为它而把你赶出家门.但他们也不会对此表示友善. (2认同)