我可以取消引用 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() 吗?的主要内容,如果未能解决你的问题,请参考以下文章

通过引用取消设置数组的元素

术语“取消引用”对象到底是啥意思?

cppcheck 取消引用空指针

我可以以任何方式从 OpenCV 对象取消引用中将数据分配给双重函数返回检索吗?

为啥取消引用称为取消引用的指针?

试图取消引用一个接口,该接口是一个指向后端结构对象的指针,以便我可以按值传递给函数