【问题标题】:C++ Iterator Exception SafetyC++ 迭代器异常安全
【发布时间】:2013-08-03 11:42:57
【问题描述】:

我对异常安全和 STL 容器/迭代器有疑问。

出于某种原因,我假设简单容器的迭代器

std::vector<POD Type> 

只要您停留在区间 [begin(), end()) 内,在对其执行算术运算(或取消引用它)时不会引发异常。我试图在标准中查找(使用 N3337),但我发现没有给出这样的 nothrow 保证(但也许我错过了一些东西!)。另见:May STL iterator methods throw an exception

到现在为止,我写了很多通常会被破坏的代码,考虑到即使对于具有合理元素类型的简单容器也没有说 nothrow 保证。

例如,以下内容可能仍会引发异常(其中 c 是 std::vector 实例):

for(... i = c.begin(); i != c.end(); ++i) { /* do something here - guaranteed to not throw. */ }

但这会导致跨不同 STD 库的异常安全和程序稳定性问题,因为据我所知,您必须了解迭代器操作的实现。

以 Boost.Graph 的邻接表的 clear() 函数为例(Boost 中还有很多这样的例子),假设容器 m_vertices 是像 std::vector 这样的标准序列容器。

inline void clear() {
for (typename StoredVertexList::iterator i = m_vertices.begin(); // begin() and copy assignement does not throw (according to the STD)
    i != m_vertices.end(); ++i) // ++i and operator != () might throw
        delete (stored_vertex*)*i; // *i might throw
    m_vertices.clear(); // will not throw (nothrow per Definition of the STD)
    m_edges.clear(); // same
}

应该保证这个函数不会抛出,因为它是在 adjacency_list<...> 的析构函数中调用的,并且假设没有 clear() 函数抛出是合理的,即使我没有找到任何异常安全保证在 Boost.Graph 的文档中。

我希望您能对这个异常安全问题有所了解,并告诉我我在这里缺少什么。尤其是对于什么样的迭代器算术运算和解引用真的不会抛出以及在哪里定义了这样的保证。

谢谢!

来自 C++ STD 论文 N3337

23.2.1:10)

除非另有说明(参见 23.2.4.1、23.2.5.1、23.3.3.4 和 23.3.6.5),本节中定义的所有容器类型 条款满足以下附加要求:

— 如果插入单个元素时 insert() 或 emplace() 函数抛出异常,则 函数没有效果。

——如果 push_back() 或 push_front() 函数抛出异常,则该函数无效。

——没有erase()、clear()、pop_back()或pop_front()函数抛出异常。

——返回的迭代器的复制构造函数或赋值运算符都不会引发异常。

——没有swap()函数抛出异常。

- 没有 swap() 函数会使任何引用、指针或迭代器的元素无效 正在交换的容器。

[ 注意:end() 迭代器不引用任何元素,所以它可能是 无效。 ——尾注]

【问题讨论】:

  • 不支持在迭代容器时调用 container.erase()。如果您这样做,实现可能会简单地崩溃或抛出异常。
  • brian beuning:我不确定你在说什么——这跟我的问题有关吗?不过,在迭代像 STD 序列容器这样的容器时调用擦除是完全有效的!
  • @brianbeuning std::vector v; /* ... 填充 v 使其不为空。 */ for(auto i = v.begin(); i!=v.end(); i=v.erase(i));断言(v.empty());但这与主题无关
  • 有 17.6.5.12“异常处理限制”,它几乎说 C++ 标准库中的函数只能抛出函数描述的 Throws: 部分中指定的内容。除了 17.6.4.8p2 “对 [用作模板参数的类型] 的操作可以通过抛出异常来报告失败,除非另有说明。”因此,除非 a) 明确允许,或 b) 从用户定义的类型传播且未明确禁止,否则不应引发异常。这是否是一个足够的保证?

标签: c++ exception stl iterator


【解决方案1】:

只有宽合约(即不可能失败的操作)没有抛出保证。所有迭代器操作都有狭窄的契约(即,它们有一些先决条件),因此,当不满足先决条件时,可能会以任意方式失败。因此,它们没有任何异常保证,因为未满足未定义的行为前提条件可能导致给定的实现抛出异常。假设满足先决条件并且行为不包括抛出任何异常,则各个迭代器操作的行为已得到很好的定义:迭代器操作的行为在需求表中定义。

也就是说,一般来说,您应该期望所有操作都可能首先抛出。要从异常中正确恢复:但是,有时需要知道特定函数不会抛出,否则恢复可能会失败,某些相当基本的操作(例如交换两个内置类型的对象)被定义为不抛出。

【讨论】:

  • 看起来我错过了 C++ 标准的一个基本点。不过,这将花费我相当长的时间来弄清楚。先生,谢谢您的出色回答!
  • Afaik 对于大多数迭代器 (counterexample) 而言,由于未满足引发异常的先决条件而导致的这种未定义行为的唯一(可疑)良好用途是让一些顶级代码捕获它调试目的。我认为这可能会导致性能下降,但会以稍微更好的调试为代价。我希望无论如何都允许标准库开发人员使用noexcept 作为迭代器。
猜你喜欢
  • 2011-06-16
  • 1970-01-01
  • 2023-03-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-03-21
相关资源
最近更新 更多