【问题标题】:std::string::reserve and end-of-string 0std::string::reserve 和字符串结尾 0
【发布时间】:2015-07-18 15:06:17
【问题描述】:

使用std::string::reserve 进行预分配时,我是否必须为终止0 显式添加一个,以避免重新分配和后续复制?

例如,知道长度为5的字符串"Hello"将存储在std::string str中,我是否必须调用str.reserve(6)

如果我正确阅读了标准,那么我认为答案应该是是的。对于reserve 它说

reserve()之后,capability()大于等于reserve的参数。

对于capacity,它依次声明

返回:字符串中分配的存储大小。

不过,我不熟悉标准中公式的微妙之处,我想证实我的怀疑。

【问题讨论】:

  • 在 C++ 11 中,零的空间是隐含的。在 C++ pre-11 中,这取决于实现。
  • 要清楚,这是一个是的,在字符串长度上加一个来保持? (在 C++11 中,我的意思是,谁在乎 pre-11 ;)
  • 不,它是“不,如果您调用 c_str(),std::string 实现将自动保留一个 [或多个] 额外字节来保存零字节以指示字符串的结尾” .当然,如果您在 pre-11 中不调用 c_str(),则无法保证字符串末尾有一个零 - 或者在这种情况下该空格不用于其他用途。但是对于 C++11,您可以保证零有空间,并且实际内容之后的字符是零。 (但请注意,这不是字符串的一部分,不会计入容量)
  • @Mats Petersson:很有趣,尤其是 11 年前的 c_str 故事。这里的措辞有点令人困惑,因为当 0 不算数时,我希望标准会在 'reserve() 之后写出容量() 严格 大于 Reserve 的参数。 '顺便说一句,背景是,从 VC2013 与 C++11 的时序来看,我的印象是加 1 确实加快了速度。但也许我必须更深入地研究 MS 的字符串实现(那么在这方面可能不符合要求?)。
  • @Mats Petersson:好的,如果我正确理解您在下面的评论,那么 0 不计入“字符串中分配的存储空间”,对吧?这再次令人困惑,但我想事情就是这样 :) 感谢您的澄清!

标签: c++ dynamic-memory-allocation stdstring


【解决方案1】:

C++11 确实指定(或者我在几个地方读过,实际上在 n3337 文档中找不到该措辞?)std::string 应该以零终止的方式存储C 风格的字符串不需要重新分配。

这当然是 GNU C++ 库中发生的事情,在函数 _S_create 中:

template<typename _CharT, typename _Traits, typename _Alloc>
  typename basic_string<_CharT, _Traits, _Alloc>::_Rep*
  basic_string<_CharT, _Traits, _Alloc>::_Rep::
 _S_create(size_type __capacity, size_type __old_capacity,
      const _Alloc& __alloc)
 ....
  // NB: Need an array of char_type[__capacity], plus a terminating
  // null char_type() element, plus enough for the _Rep data structure.
  // Whew. Seemingly so needy, yet so elemental.
  size_type __size = (__capacity + 1) * sizeof(_CharT) + sizeof(_Rep);

+ 1 用于覆盖终止字符。

然后它继续“调整”大小以使其更优化,使用minimum allocationpage_size 的一些猜测常量,但它始终至少为__size,并始终添加1为终止腾出空间。

要弄清楚它“总是”这样做,您必须遵循代码并发现每当需要重新分配字符串时都会调用_M_clone,而_M_clone 又会调用_S_create。代码不容易阅读,因为它是为了遵循标准和高效而编写的,而不是为了让我们这些凡人阅读它。

这里更容易看到c_str 没有分配任何东西:

  const _CharT*
  c_str() const _GLIBCXX_NOEXCEPT
  { return _M_data(); }

然后调用:

  _CharT*
  _M_data() const _GLIBCXX_NOEXCEPT
  { return  _M_dataplus._M_p; }

换句话说,只是返回指向实际字符串的指针。

【讨论】:

  • Visual C++ 的实现也是一样,它也为终止的 0 分配了额外的存储空间。感谢 GNU C++ 库的分析。
  • 关于标准的问题仍然存在?重读n4296的string-section,我想这一切都归结为capability()的含义。而且我认为正式的定义(“分配的存储空间”)在这里具有误导性,因为在其他部分使用 capacity() 加上容器级别的 size() 的含义清楚地表明为 user 数据(即,没有终止的 0)是它想要表达的意思。你同意吗?或者你有这方面的明确声明?
  • 该标准还规定 c_str() 应该在恒定时间内发生 - 换句话说,无论里面的字符串是 1 还是 1 百万,它必须花费相同的时间来执行 c_str()字符长 - 这意味着 [虽然它在技术上没有说明] 在 c_str 内不应该有复制操作。很明显,capacity() 的功能是指示用户拥有多少存储空间,而不是内部表示占用的字节数。
  • c_str() 不能触发复制从它的复杂性中是显而易见的,当然,但不适用于赋值。我只是不太熟悉标准中的语言,所以感谢您的澄清。另一方面,标准中的一般要求,例如“如果容量()足够大,则必须没有分配”会很好;如果我做对了,一个(非常糟糕的)符合标准的实现现在可能会重新分配,即使它有足够的空间。
  • 为了“允许替代实现”的目的,标准文档通常含糊不清。如果您在标准文档中规定过多,则意味着无法使用某些优化或硬件受限的解决方案,这反过来又会限制语言的实用性——正如规范所言,C++ 规范实际上并没有那么糟糕.
【解决方案2】:
Returns: The size of the allocated storage in the string.

Allocated storage in the string 该语句意味着字符串应该适合分配的空间。 “字符串”表示“以零结尾的字符组”,因此也应包含零。

【讨论】:

  • 这就是capacity() 返回的结果,但是必须将什么数字传递给reserve 以避免在持有"HELLO" 时分配?
  • 我读起来更像是字符串对象真正分配的空间(虽然可能只是堆上的部分)。当然,哪个必须包含终止 0 才能提供老式的std::string::c_str
  • c_str 添加的零不是实际存储的字符串的一部分 - 但是,就像我上面所说的,在 C++11 中,它会有空间,并且保证“在那里” [显然在第一个线程的更新期间访问第二个线程中的字符串,您不能保证字符串始终终止 - 您需要在字符串函数中锁定以保证 - 并且 std::string 不会'没有这样的东西]
  • 根据open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4416.pdfreserve()之后,capacity() 大于或者等于reserve的参数。所以如果“你好”这个词,给5就足够了。
  • @Tomasz Pluta:当然实现可以选择实际让容量更大;但它 is 允许它相等。在后一种情况下,根据分配空间的数量(见上文),可能需要重新分配才能将 0 存储在某处; "Hello" 已经占用了所有预先分配的 5 个字符存储空间。
猜你喜欢
  • 2015-08-11
  • 2019-02-22
  • 1970-01-01
  • 2019-09-10
  • 1970-01-01
  • 2010-10-12
  • 2015-01-04
  • 2022-11-23
  • 1970-01-01
相关资源
最近更新 更多