Re: Is exception-safety possible at all?
I am replying to myself because same arguments and thoughts appeared
in multiply posts and it would not be fair to replay to just one of
them (while repeating is obviously bad). Also I wanted to add some
clarifications because it seems that in few points I either used a bad
example or was not understood correctly.
When I was mentioning that Standard does say to little (IMHO) about
exceptions guarantees of STD and primitive operations I didn't want
the Standard to name exception types to be thrown. I just wanted to
have better descriptions like "throws when" and more "throws nothing".
I am aware of fact that knowledge of exception types is not required
to gain exception safety.
Also please note that I mentioned "portable" few times. It was on
purpose. Obviously I could check in my implementation's documentation
whether mentioned "empty" throws or not. But this info is of little
use if the code is to be ported to a different implementation. Thus
the only information I can relay on (when trying to be portable) is
the Standard alone.
Another thing is that I do not request "throw(*something*)" to be
added to functions of STD. I just want the Standard to put more
restrictions/regulations/no-throws.
And when requesting that library and generally implementation supplied
code throws only std::exception (or derived) types I obviously meant
exceptions caused by internals of implementation. If user supplied
code throws something else it is the choice of the user.
Using example of division by 0 and overflow was unfortunate. Obviously
this is undefined behavior. But let us not be blinded by a bad
example. I will latter in this post give a better example and lets
forget about that one.
So lets consider a different example:
class Operation
{
public:
void commit(); // may throw
void rollback(); // will never throw
};
void f( std::vector< Operation >& vec )
{
typedef std::vector< Operation >::iterator iterator;
typedef std::vector< Operation >::reverse_iterator
reverse_iterator;
const iterator end = vec.end(); // (1)
iterator it = vec.begin(); // (2)
try
{
for ( ; it != end; ++it ) // (3)
it->commit(); // (4)
}
catch ( ... )
{
const reverse_iterator rend = vec.rend(); // (5)
reverse_iterator rit = it; // (6)
for ( ; rit != rend; ++rit ) // (7)
rit->rollback(); // (8)
}
}
From my personal experience this code is considered to be exception-
safe (basic guarantee). If "commit" and "rollback" are understood
according to intuition then this is exception-safe code in the strong
meaning.
But in my opinion it is not. This is why:
- (5) may fail. Although 23.1$10 says that "no copy constructor or
assignment operator of a returned iterator throws an exception" it
does not say so about "rend" (or "rbegin", "begin" and "end"...). And
in fact for some containers it might fail (because of lack of memory
for example for some data required by the iterator). But Standard does
not put any restriction on vector so for vector it might fail as well.
- (6) may fail. Construction of "reverse_iterator" from iterator may
throw.
- (7) may fail. operators != and ++ might both throw.
- (8) may fail. operator -> might throw.
Thus this code (in "catch") might very well not be executed correctly
to the end. Strong guarantee is lost. Also whil throwing it will mask
original exception so transparency is lost. Basic guarantee is held.
However i suppose I could find a more fancy example where even the
basic guarantee would be lost.
Obviously switching to iterating with -- in "catch" will not change
much as that could still could throw. The same is true when iterating
with index types. First of all index type for vector is size_type and
it might be any type - not only basic type (if I am not mistaken).
Further more even if it would be integer type any arithmetic operation
or comparison might throw as well (even if for valid arguments)
because Standard does not prohibit it (or does it? - where then?).
Adam Badura
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]