1

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

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

std::vector<POD Type> 

只要您保持在区间 [begin(), end()) 内,就不会在对其执行算术运算(或取消引用它)时引发异常。我试图在标准中查找它(使用 N3337),但我发现没有给出这样的 nothrow 保证(但也许我错过了一些东西!)。另请参阅:May STL 迭代器方法抛出异常

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

例如,以下内容可能仍会引发异常(其中 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 这样的 std 序列容器。

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() 迭代器不引用任何元素,因此它可能会失效。——尾注]

4

1 回答 1

3

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

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

于 2013-08-03T13:51:08.303 回答