Solution
Exception Safety and the Standard
Library
Are the standard library containers
exception-safe and exception-neutral? The short answer is:
Yes.
-
All iterators returned from standard containers
are exception-safe and can be copied without throwing an
exception.
-
All standard containers must implement the basic
guarantee for all operations: They are always destructible, and
they are always in a consistent (if not predictable) state even in
the presence of exceptions.
-
To make this possible, certain important
functions are required to implement the nothrow guarantee (are
required not to throw)—including swap (the importance of
which was illustrated by the example in the previous Item),
allocator<T>::deallocate (the importance of which
was illustrated by the discussion of operator delete() at
the beginning of this miniseries) and certain operations of the
template parameter types themselves (especially, the destructor,
the importance of which was illustrated in Item 16 by the discussion headed
"Destructors That Throw and Why They're Evil").
-
All standard containers must also implement the
strong guarantee for all operations (with two exceptions). They
always have commit-or-rollback semantics so that an operation such
as an insert either succeeds completely or else does not
change the program state at all. "No change" also means that failed
operations do not affect the validity of any iterators that
happened to be already pointing into the container.
-
There are only two exceptions to this point.
First, for all containers, multi-element inserts ("iterator range"
inserts) are never strongly exception-safe. Second, for
vector<T> and deque<T> only, inserts
and erases (whether single- or multi-element) are strongly
exception-safe as long as T's copy constructor and
assignment operator do not throw. Note the consequences of these
particular limitations. Unfortunately, among other things, this
means that inserting into and erasing from a
vector<string> or a
vector<vector<int> >, for example,
are not strongly exception-safe.
-
Why these particular limitations? Because to
roll back either kind of operation isn't possible without extra
space/time overhead, and the standard did not want to require that
overhead in the name of exception safety. All other container
operations can be made strongly exception-safe without overhead. So
if you ever insert a range of elements into a container, or if
T's copy constructor or assignment operator can throw and
you insert into or erase from a vector<T> or a
deque<T>, the container will not necessarily have
predictable contents afterward and iterators into it may have been
invalidated.
What does this mean for you? Well, if you write
a class that has a container member and you perform range
insertions, or you write a class that has a member of type
vector<T> or deque<T>, and
T's copy constructor or assignment operator can throw,
then you are responsible for doing the extra work to ensure that
your own class's state is predictable if exceptions do occur.
Fortunately, this "extra work" is pretty simple. Whenever you want
to insert into or erase from the container, first take a copy of
the container, then perform the change on the copy. Finally, use
swap to switch over to using that new version after you
know that the copy-and-change steps have succeeded. |