1

I have a problem with exception safety and STL containers/iterators.

I assumed for some reason that an iterator of the simple container

std::vector<POD Type> 

is not throwing an exception when performing arithmetic operations on it (or deref. it) as long as you stay within the interval [begin(), end()). I tried to look that up in the standard (using N3337) but i have found that no such nothrow guarantees are given (but maybe i missed something!). Also see: May STL iterator methods throw an exception

Until now I wrote quite some code that would be broken in general, taking into account that there are no said nothrow guarantees even for simple containers with reasonable element types.

For example something like the following might still throw an exception (whereby c is a std::vector instance):

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

But this incurs exception safety and program stability problems across different STD libraries since you have to know the implementations of the iterator operations as far as I can see.

For example take the clear() function of Boost.Graph's adjacency list (and there are many more such examples within Boost) and suppose the container m_vertices is a std sequence container like 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
}

This function should be guaranteed to not throw since it is called in the destructor of adjacency_list<...> and it would be reasonable to assume that no clear() function throws, even though I did not find any exception safety guarantees in the docs of Boost.Graph.

I hope you can shed some light onto this exception safety issue and show me what I am missing here. Especially for what kind of iterators arithmetic operations and dereferencing is really not throwing and where such guarantees are defined.

Thanks!

From the C++ STD Paper N3337

23.2.1:10)

Unless otherwise specified (see 23.2.4.1, 23.2.5.1, 23.3.3.4, and 23.3.6.5) all container types defined in this Clause meet the following additional requirements:

— if an exception is thrown by an insert() or emplace() function while inserting a single element, that function has no effects.

— if an exception is thrown by a push_back() or push_front() function, that function has no effects.

— no erase(), clear(), pop_back() or pop_front() function throws an exception.

— no copy constructor or assignment operator of a returned iterator throws an exception.

— no swap() function throws an exception.

— no swap() function invalidates any references, pointers, or iterators referring to the elements of the containers being swapped.

[ Note: The end() iterator does not refer to any element, so it may be invalidated. —end note ]

Community
  • 1
  • 1
  • Calling container.erase() while you are iterating over the container is not supported. An implementation may simply crash or it may throw an exception if you do this. – brian beuning Aug 03 '13 at 12:23
  • brian beuning: I am not sure what you are saying here - has it something to do with my question? calling erase while iterating over a container like a STD sequence container is perfectly valid though! – livingissuicide Aug 03 '13 at 12:38
  • See http://stackoverflow.com/questions/6438086/iterator-invalidation-rules – brian beuning Aug 03 '13 at 12:48
  • @brianbeuning std::vector v; /* ... fill v so it isn't empty. */ for(auto i = v.begin(); i!=v.end(); i=v.erase(i)); assert(v.empty()); But this has **nothing** to do with the topic! – livingissuicide Aug 03 '13 at 12:59
  • There's 17.6.5.12 "Restrictions on exception handling", which pretty much says that functions in C++ standard library can only throw what's specified in the Throws: section of the function's description. Except that 17.6.4.8p2 "Operations on [types used as template arguments] can report a failure by throwing an exception unless otherwise specified." So, no exceptions should be thrown unless a) explicitly allowed, or b) propagate from user-defined types and not explicitly suppressed. Would this be a sufficient guarantee? – Igor Tandetnik Aug 03 '13 at 13:55

1 Answers1

3

Only wide contracts (i.e., operations which can't possibly fail) are given no throw guarantees. All iterator operations have narrow contracts (i.e., they have some precondition), and thus, can fail in arbitrary ways when the preconditions aren't met. Thus, they don't have any exception guarantees, because the undefined behavior preconditions aren't met may result in a given implementation throwing an exception. The behavior of the individual iterator operations are well defined assuming the preconditions are met and the behavior doesn't include throwing any exception: the behavior of the iterator operations is defined in the requirement tables.

That said, in general, you should expect all operations to potentially throw in the first place. To do proper recovery from exception: it is, however, sometimes necessary to know that specific functions won't throw because otherwise the recovery might fail, certain rather basic operations like swapping two objects of a built-in type are defined to not throw.

rstackhouse
  • 2,238
  • 24
  • 28
Dietmar Kühl
  • 150,225
  • 13
  • 225
  • 380
  • Looks like that I have missed a fundamental point of the C++ standard. This would have taken me quite some time to figure out though. Thank you, sir, for this excellent answer! – livingissuicide Aug 04 '13 at 11:02
  • Afaik for most iterators ([counterexample](http://stackoverflow.com/a/7903037/3919155)) the only (questionable) good use for this undefined behavior caused by preconditions not being met to throw an exception would be for some top-level code to catch it for debugging purposes. I think this may cause lower performance at the expense of slightly better debugging. I hope that standard library developers are allowed to use `noexcept` for the iterators anyway. – jotik Mar 28 '16 at 09:43