我可以取消引用 std::string.end() 吗?
Posted
技术标签:
【中文标题】我可以取消引用 std::string.end() 吗?【英文标题】:Can I dereference std::string.end()? 【发布时间】:2020-05-07 23:59:17 【问题描述】:我相信对此的常见反应是“否”,因为容器的 end()
迭代器代表了一个“过去的”地址,它是取消引用的未定义行为。我在标准中找不到明确的声明可以免除字符串不受此约束,即使字符串在其他容器上具有特殊情况。
C++11 标准声明您可以读取字符串末尾的一个索引。 string[size()]
引用空终止符的只读值。
24.3.2.5 basic_string 元素访问[string.access]
const_reference operator[](size_type pos) const;
reference operator[](size_type pos);
(1) 需要:
pos <= size()
。(2) 返回:
*(begin() + pos) if pos < size()
。否则,返回对类型对象的引用charT
的值为charT()
,其中将对象修改为charT()
以外的任何值会导致 未定义的行为。
front()
被定义为等同于return operator[](0)
,对于空字符串,它等同于return operator[](size())
。
end() - begin()
被明确定义为字符串长度的差异,因此end()
必须指向size()
的索引,以便合理实现来定义该算术。
在上面的标准摘录中,它声明operator[](pos)
等价于*(begin() + pos)
如果pos < size()
。它确实没有说您可以取消引用begin() + size()
,但您认为假设这应该被明确定义是否合理?或者更好的是,您是否知道一些使字符串迭代器不受约束的证明?
另外,是否可以证明任何i
的*(begin() + i)
等价于operator[](i)
?
【问题讨论】:
取消引用 any 容器的end()
迭代器是不合法的,包括 std::string
。从逻辑上讲,end()
可以引用字符串的空终止符,C++11 要求它存在于内存中。但是end()
不需要引用内存中的 actual 空终止符。唯一的要求是operator[](size())
引用“一个charT
类型的对象”,它可以很容易地定义在string
对象之外的静态char(0)
,这很重要对于为空字符串存储 nullptr
char*
指针的实现。这不会改变 end()
的行为方式。
@RemyLebeau 因此我对“理智”实现的注释。如果end()
指向远离字符串内存的静态值,则实现将需要在整个地方检查这种情况,以处理简单的事情,例如end() - 1
,它应该指向字符串中的最后一个字符。我知道这对于任何容器通常都是非法的,但也许应该对字符串进行纠正?问题是是否已经有我找不到的证据。
我希望“理智”的实现使 end()
指向空终止符的地址,以保持简单。即使支持它的内存是有效的,这仍然不能使 end()
合法地取消引用。使std::string::end()
迭代器成为一种特殊情况,其行为与其他容器不同,只会使设计用于任何容器的算法复杂化。没有充分的理由永远取消引用 std::string::end()
迭代器。所以我认为标准中没有什么可以“纠正”的。
@RemyLebeau operator[](size())
已经是字符串容器的特例,对于其他容器是非法的。 string
有一个特殊情况,你可以读完一个;为什么纠正标准以使end()
行为相同没有意义?
@RemyLebeau Until C++11, std::string
was not required to include a trailing nul until you called c_str()
。例如,Facebook 也实现了自己的 std::string
,没有空终止符:The strange details of std::string at Facebook
【参考方案1】:
来自string.end()的定义:
返回:一个迭代器,它是 past-the-end 值。
从past-the-end的定义来看:
... 这样的值称为过去值。定义了表达式 *i 的迭代器 i 的值称为可解引用。库从不假定过去的值是可取消引用的。 ...
重点是我的,我猜想std::string
的任何例外都会在第一个链接中提及。既然不是,取消引用std::string.end()
是未定义的遗漏。
【讨论】:
我们可以说它是由遗漏定义的,因为似乎没有定义取消引用行为的子句 @M.M 我喜欢这个措辞,谢谢 :) 将其添加到答案中。【参考方案2】:在std::string
的情况下似乎应该是可能的,因为按理说它是一个空终止符,但它仍然是未定义的行为:
https://en.cppreference.com/w/cpp/string/basic_string/end
将迭代器返回到字符串最后一个字符之后的字符。此字符充当占位符,尝试访问它会导致未定义的行为。
在字符串库中,我找不到指向任一方向的引用,但是由于我们正在讨论迭代器,因此迭代器库定义:
©ISO/IECN4659 § 27.2.1 - 7
...对于任何迭代器类型,都有一个迭代器值指向对应序列的最后一个元素。这些值称为过去值...库从不假定过去值是可取消引用的...
【讨论】:
对于语言律师问题,最好引用标准,而不是非官方参考网站。 虽然众所周知,取消引用容器的尾端通常是非法的/未定义的,但我认为有趣的是该标准并未在此处明确说明这一点。它声明图书馆假设它是,即图书馆不会那样做。这是否排除了 std::string 具有有效的 end() 迭代器,与库的假设无关? 不,标准可以为std::string
设置一个例外,但它没有。此外,从不假设部分可能必须更改。
@cigien: "另外,从不假设部分可能必须更改。" 但是您可以将可取消引用的迭代器作为范围的结束迭代器传递,并且这个仍然是真的。它所描述的是禁止 库 将如何使用结束迭代器,而不是禁止结束迭代器实际上是什么。
@mukunda,肯定很有意思,但前提是肯定做不到,可能是发现iterators库里的引用就够了,不用在里面提了字符串库,从不假设部分为实现定义的行为打开了大门,这并不罕见。以上是关于我可以取消引用 std::string.end() 吗?的主要内容,如果未能解决你的问题,请参考以下文章