是否允许std :: string的end + 1迭代器?

时间:2015-11-11 18:10:46

标签: c++ string iterator language-lawyer stdstring

end(str)+1创建std::string的迭代器是否有效? 如果不是,为什么不是吗?

这个问题仅限于C ++ 11及更高版本,因为在C ++ 11之前,数据已经存储在任何罕见的POC玩具实现的连续块中,数据不会 以这种方式存储。
而且我认为这可能会有所不同。

std::string与我推测的任何其他标准容器之间的显着差异在于它总是包含一个元素,而不是size,零终止符,以满足{{1}的要求}。

  

21.4.7.1 basic_string访问者 [string.accessors]

.c_str()
     

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

尽管如此 imho保证所述表达式是有效的,为了保持与零终止字符串的一致性和互操作性,如果没有别的,我发现的唯一段落对此表示怀疑:

  

21.4.1 basic_string一般要求 [string.require]

     

4 i对象中类似char的对象应连续存储。也就是说,对于任何[0,size()]对象basic_stringbasic_string的所有值都应保留s&*(s.begin() + n) == &*s.begin() + n

(所有引用均来自C ++ 14最终草案(n3936)。)

相关:Legal to overwrite std::string's null terminator?

4 个答案:

答案 0 :(得分:10)

TL; DR: s.end() + 1是未定义的行为。

std::string是一种奇怪的野兽,主要是出于历史原因:

  1. 它试图引入C兼容性,因为已知\0报告的长度超出strlen字符。
  2. 它的设计采用了基于索引的界面。
  3. 作为一个想法,当在标准库中与其余的STL代码合并时,添加了一个基于迭代器的接口。
  4. 这导致{+ 1}}在C ++ 03中编号为103 member functions,从那以后添加了一些。

    因此,应该预期不同方法之间存在差异。

    已经在基于索引的界面中出现差异:

      

    §21.4.5[string.access]

         

    std::string
      const_reference operator[](size_type pos) const;

         

    1 / 需要: reference operator[](size_type pos);

         

    pos <= size()   const_reference at(size_type pos) const;

         

    5 / 投掷: reference at(size_type pos);如果out_of_range

    是的,你看对了,pos >= size()会返回对NUL字符的引用,而s[s.size()]会抛出s.at(s.size())例外。如果有人告诉您将out_of_range的所有用途替换为operator[]因为它们更安全,请注意at陷阱......

    那么,迭代器呢?

      

    §21.4.3[string.iterators]

         

    string
      iterator end() noexcept;
      const_iterator end() const noexcept;

         

    2 / 返回:一个迭代器,它是过去的值。

    非常平淡。

    所以我们必须参考其他段落。

    提供指针
      

    §21.4[basic.string]

         

    3 / const_iterator cend() const noexcept;支持的迭代器是随机访问迭代器(24.2.7)。

    虽然§17.6[要求] 似乎没有任何相关内容。因此,字符串迭代器只是普通的旧迭代器(你可能会感觉到它的发展方向......但是因为我们走到了这一步,所以一路走下去。)

    这导致我们:

      

    24.2.1 [iterator.requirements.general]

         

    5 / 正如指向数组的常规指针一样,保证指针值指向数组的最后一个元素,因此对于任何迭代器类型,都有一个指向过去的迭代器值相应序列的最后一个元素。这些值称为 past-the-end 值。定义了表达式basic_string的迭代器i的值称为可解除引用。库永远不会假定 past-the-end 值是可解除引用的。 [...]

    所以,*i格式不正确。

      

    24.2.3 [input.iterators]

         

    2 / 表107 - 输入迭代器要求(除迭代器外)

    列出*s.end()++r r++可以解除引用的前提条件。

    Forward迭代器,Bidirectional迭代器和Random迭代器都没有解除这个限制(并且都表明它们继承了前任的限制)。

    另外,为了完整性,在 24.2.7 [random.access.iterators] 表111 - 随机访问迭代器要求(除了双向迭代器)列表以下操作语义:

    • r相当于[inc | dec]记住r += n r
    • na + n相当于复制n + a,然后将a应用于副本

    ,同样适用于+= n-= n

    因此- n是未定义的行为。

答案 1 :(得分:5)

  

返回:指针pp + i == &operator[](i)中每个i的{​​{1}}。

指定

[0,size()]std::string::operator[](size_type i)时返回“对charT类型的对象的引用,其值为charT(),因此我们知道该指针指向一个对象。

5.7声明“出于[operator +和 - ]的目的,指向非阵列对象的指针与指向长度为1的数组的第一个元素的指针的行为相同,其中对象的类型为其元素类型。“

所以我们有一个非数组对象,并且规范保证一个经过它的指针可以表示。所以我们知道i == size()必须具有代表性。

然而,这不是std::addressof(*end(str)) + 1的保证,并且规范中的任何地方都没有这样的保证,这使其成为未定义的行为。

(请注意,这与'格式错误'不同。std::string::iterator实际上是格式良好的。)

当你执行诸如递增*end(str) + 1迭代器之类的操作时,迭代器可以并且确实实现了产生各种错误的检查逻辑。这实际上是Visual Studios调试迭代器对end()执行的操作。

end(str) + 1
  

如果不是,为什么不是吗?

     

与零终止字符串的一致性和互操作性,如果没有别的

C ++指定了与C兼容的一些特定内容,但这种向后兼容性仅限于支持实际用C语言编写的内容.C ++不一定试图采用C语义并使新结构以某种类似的方式运行。 #define _ITERATOR_DEBUG_LEVEL 2 #include <string> #include <iterator> int main() { std::string s = "ssssssss"; auto x = std::end(s) + 1; // produces debug dialog, aborts program if skipped } 应该衰减到迭代器只是为了与C的数组衰减行为一致吗?

我会说std::vector被保留为未定义的行为,因为尝试以这种方式约束end(std) + 1迭代器没有任何价值。没有传统的C代码能够实现这一点,C ++需要与之兼容,并且应该阻止新代码执行此操作。

  

应该阻止新代码依赖它......为什么? [...]什么不允许它在理论上买你,这在实践中看起来如何?

不允许它意味着实现不必支持增加的复杂性,复杂性提供零证明值。

事实上,在我看来,支持std::string具有负值,因为尝试使用它的代码实际上会产生与C代码相同的问题,而C代码无法确定何时考虑空终止符或不。对于两种语言,C都有足够的缓冲区大小错误。

答案 2 :(得分:2)

std::basic_string<???>是其元素的容器。它的元素不包含隐式添加的尾随空值(它可以包含嵌入的空值)。

这对于这个字符串中的每个字符都很有意义 - &#34;&#34;可能不应该返回尾随'\0',因为这实际上是与C风格API兼容的实现细节。

容器的迭代器规则基于容器,它不会在最后推出额外的元素。在没有动机的情况下为std::basic_string<???>修改它们是值得怀疑的;如果有收益,就应该打破工作模式。

我们完全有理由认为允许指向.data().data() + .size() + 1的指针(我可以想象一种对标准的扭曲解释会使其不被允许)。因此,如果你真的需要只读迭代器到std::string的内容中,你可以使用指针到const元素(毕竟它是一种迭代器)。

如果你想要可编辑的那些,那么不,没有办法得到一个有效的迭代器到一个接一个。你也不能合法地获得非const对尾随空的引用。事实上,这种访问显然是一个坏主意;如果更改该元素的值,则会中断std::basic_string不变的空终止。

为了有一个结尾的迭代器,容器的const和非const迭代器必须具有不同的有效范围,或者是最后一个元素的非const迭代器。取消引用但不写入必须存在。

我对这种标准措辞的防水感到不寒而栗。

std::basic_string已经一团糟了。让它更奇怪会导致标准错误,并且会产生非常重要的成本。好处很低;在您希望在迭代器范围内访问所述尾随null的少数情况下,您可以使用.data()并将结果指针用作迭代器。

答案 3 :(得分:1)

我找不到明确的答案,但间接证据表明 end()+1 未定义。

<块引用>

[string.insert]/15

constexpr iterator insert(const_iterator p, charT c);
前提条件:p*this 上的有效迭代器。

期望它使用 end()+1 作为迭代器是不合理的,它确实导致 libstdc++ 和 libc++ 崩溃。

这意味着 end()+1 不是有效的迭代器,这意味着 end() 不可递增。