Re: Is exception-safety possible at all?
On Jan 19, 8:57 am, Adam Badura <abad...@o2.pl> wrote:
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.
Well, you are exaggerating, but you are ultimately right! I am not
going to start worrying about that possible throws from end, begin,
rend, rbegin and these particular !=, ++ and ->but for the purpose of
discussion, let's say, that it __is__ the case.
You could do (warning: compiled with the head-compiler and tested with
head-debugger ;-) ):
typedef std::vector< Operation > Vec;
void f( Vec& vec )
{
boost::scoped_array<Operation*> Array(new Operation[vec.size()])
Operation** Operations = Array.get();
Operation** OperationsEnd = Operations+vec.size();
for (Vec::size_type i = 0; i<vec.size(); ++i)
PArray[i] = &vec.at(i);
// From here on, you have your "nothrow commit-phase" data.
Vec::iterator end = vec.end();
Vec::iterator it = vec.begin();
try
{
for ( ; it != end; ++it )
it->commit();
}
catch ( ... )
{
while (OperationsEnd != Operations)
(*(OperationsEnd--)->rollback();
throw; // Ah-ha! You forgot that, didn't you? ;-)
}
}
BTW, in reality, it's probably the case that rollback() is to be
called only up to the last successful call to commit(), so even the
"commit" loop would do well to use some sort of Operations/
OperationsEnd/HighWatermark approach.
IOW... If, at any time, you suspect exceptions, drop down to primitive
types and a non-throwing swap. That's all there is to it. No, really!
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]