25

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

basic_string 对象中的类字符对象应连续存储。也就是说,对于任何 basic_string 对象 s,标识 &*(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 节):

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

即便如此,迭代器可能会失效,但我们仍然可以修改它们,直到它们失效。我们仍然可以使用指针,直到它也无法从缓冲区中读取。

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

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

std::string text;
text.resize(GetTextLength());
GetText(text.data());
4

1 回答 1

36

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

我将说明明显的一点:因为它是const. 抛弃一个const值然后修改该数据是……粗鲁的。

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

至于为什么还在 const?嗯......这在标准中出现了一个奇怪的东西:空终止符。

这是合法的代码:

std::string stupid;
const char *pointless = stupid.c_str();

pointless必须是一个以 NUL 结尾的字符串。具体来说,它必须是指向 NUL 字符的指针。那么 NUL 字符是从哪里来的呢?实现有几种方法std::string可以让它工作:

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

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

当然,你仍然可以通过do获得这样的指针&str[0],但是标准非常明确,修改NUL终止符是一个坏主意

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

这对你来说是标准的。

于 2013-01-12T07:14:32.810 回答