为什么STL中允许未定义的行为

Why is undefined behavior allowed in the STL?

本文关键字:未定义 STL 为什么      更新时间:2023-10-16

默认情况下,std::stack的"底层容器"是std::deque。因此,对于std::deque而言,任何未定义的行为都是对于std::stack而言的未定义行为。cppreference和其他站点在描述成员函数的行为时使用术语"有效"。我认为这意味着它是为了所有的意图和目的。因此,调用top()pop()等同于调用back()pop_back(),在空容器上调用它们是未定义的行为。

根据我的理解,它之所以是未定义的行为,是为了保留无投掷保证。我的推理是,std::vectoroperator[]有无抛出保证,如果容器大小大于N,则是未定义的行为,但at()有强保证,如果N超出界限,则抛出std::out_of_range

所以我的问题是,有些事情可能有未定义的行为,并有一个不抛出保证,而有一个强有力的保证,但却抛出了一个异常,这背后的理由是什么?

当允许未定义的行为时,出于效率的原因,通常是。

如果标准指定了访问越界数组时必须执行的操作,则会强制实现检查索引是否在边界内。向量也是如此,它只是一个动态数组的包装器。

在其他情况下,允许对行为进行未定义,以便在实施中允许自由。但这也关乎效率(因为一些可能的实现策略在某些机器上可能比在其他机器上更高效,而C++则让实现者自己选择最高效的策略,如果他们愿意的话。)

根据Herb Sutter的说法,一个明显的原因是效率。他指出,该标准没有对operator[]的异常规范或是否需要绑定检查提出任何要求。这取决于实施。

另一方面,允许vector<T>::operator[](),但不允许必需,以执行边界检查。一点措辞都没有在operator[]()的标准规范中关于边界检查,但也没有任何要求有一个异常规范,所以您的标准库实现程序也可以自由地向operator[]()添加边界检查。所以,如果你使用operator[]()要求一个不在向量中的元素而标准并不能保证发生(尽管您的标准库实现的文档may)--您的程序可能会立即崩溃,调用operator[]()可能会抛出异常,或者事情似乎正常偶尔和/或神秘地失败。

考虑到边界检查保护我们免受许多常见问题的影响,为什么operator[]()不需要执行边界检查?这个简短的回答是:效率。总是检查边界会导致所有程序的性能开销(可能很小),即使是那些永远不要越界。C++的精神包括这样一句格言:总的来说,你不应该为你不使用的东西付费,所以operator[]()不需要边界检查。在这种情况下,我们有一个额外的理由想要效率:矢量是有意的使用,而不是内置数组,因此应该同样高效作为内置数组,不进行边界检查。如果你想成为确保检查了边界,请改用at()

如果您对性能优势感到好奇,请参阅以下两个问题:

  1. ::std::vector::at()vs运算符[]<lt;令人惊讶的结果!!速度慢5到10倍/更快
  2. vector::at与vector:,运算符[]

人们一致认为operator[]更高效(因为std::vector只是动态数组的包装器,所以operator[]应该和在数组上调用它一样高效。)Herb Sutter似乎认为它是否是异常安全的取决于编译器供应商。