RAII and cleanup functions that can throw

K

Kenneth Porter

I've read this article and have some followup questions.

http://groups.google.com/group/comp.lang.c++.moderated/browse_thread/thr
ead/9d5324ce02f4d89b/

I'm working on an embedded robotics application that cannot terminate.

I often have the need to temporarily change some setting before
performing an operation. If the operation fails, I still need to restore
the original setting. It's possible for the restore operation to fail
(eg. a communication fault that temporarily prevents the request from
reaching the hardware), in which case user intervention is required. The
application can later re-initialize the system to a known state.

A typical scenario, written in C (assuming a zero errorcode means no
error):

int oldvalue;
int errorcode = SaveOldValue(&oldvalue);
if (!errorcode)
errorcode = SetValue(newvalue);
if (!errorcode)
errcode = PerformOperation();
int errcode2 = SetValue(oldvalue);
return errcode ? errcode : errcode2;

This idiom might be nested.

With C++, it seems like I should report errors via exceptions, and
perform the temporary settings changes with RAII objects (to insure that
original settings are restored through all error paths).

How can I capture the error if the restore operation fails? Do I simply
use uncaught_exception() in the RAII dtor to throw only if I'm not
handling an exception from the main operation?
 
A

Alf P. Steinbach

* Kenneth Porter:
I've read this article and have some followup questions.

http://groups.google.com/group/comp.lang.c++.moderated/browse_thread/thr
ead/9d5324ce02f4d89b/

I'm working on an embedded robotics application that cannot terminate.

I often have the need to temporarily change some setting before
performing an operation. If the operation fails, I still need to restore
the original setting. It's possible for the restore operation to fail
(eg. a communication fault that temporarily prevents the request from
reaching the hardware), in which case user intervention is required. The
application can later re-initialize the system to a known state.

A typical scenario, written in C (assuming a zero errorcode means no
error):

int oldvalue;
int errorcode = SaveOldValue(&oldvalue);
if (!errorcode)
errorcode = SetValue(newvalue);
if (!errorcode)
errcode = PerformOperation();
int errcode2 = SetValue(oldvalue);
return errcode ? errcode : errcode2;

This idiom might be nested.

With C++, it seems like I should report errors via exceptions, and
perform the temporary settings changes with RAII objects (to insure that
original settings are restored through all error paths).

How can I capture the error if the restore operation fails? Do I simply
use uncaught_exception() in the RAII dtor to throw only if I'm not
handling an exception from the main operation?

Note that the C-style code above "solves" the double exception/error
problem by, in the last line, discarding errcode2 (the SetValue error)
if any operation before that fails.

To do /the same/ using C++ exceptions instead of error codes:

struct ValueUsageWrapper
{
int myOriginal;
bool myShouldReportRestoreError;

static int current()
{
int v;
if( SaveOldValue( &v ) != 0 ) { throw ValueAccessException(); }
return v;
}

static void set( int v )
{
if( SetValue( v ) != 0 ) { throw ValueAccessException(); }
}

ValueUsageWrapper()
: myOriginal( current() )
, myShouldReportRestoreError( false )
{}

~ValueUsageWrapper()
{
try
{
set( myOriginal );
}
catch( ... )
{
if( myShouldReportRestoreError ) { throw; }
}
}

void reportAnyRestoreError() { myShouldReportRestoreError = true; }
};

void performOperationOrX()
{
if( PerformOperation() != 0 ) { throw SomeThing(); }
}

void foo( int newValue )
{
ValueUsageWrapper valueUsage;

valueUsage.set( newValue );
performOperationOrX();
valueUsage.reportAnyRestoreError();
}

But again, note that by design -- yours!, the C-style code ;-) --
the client code will not be aware of failure to restore the value if any
operation before that fails. This is discussed in the FAQ. As an
exercise, find the relevant FAQ item.
 
?

=?ISO-8859-15?Q?Juli=E1n?= Albo

Kenneth said:
I often have the need to temporarily change some setting before
performing an operation. If the operation fails, I still need to restore
the original setting. It's possible for the restore operation to fail
(eg. a communication fault that temporarily prevents the request from
reaching the hardware), in which case user intervention is required. The
application can later re-initialize the system to a known state. (snip)
How can I capture the error if the restore operation fails? Do I simply
use uncaught_exception() in the RAII dtor to throw only if I'm not
handling an exception from the main operation?

IMO will be better that the system works as finite state automata, and set
the system state to "Intervention required". By means of a global function,
of by having in the RAII object a reference to the object that maintains
the state, or any other way appropriate for your application. The general
rule is to avoid to throw from destructors.
 
R

Roland Pibinger

With C++, it seems like I should report errors via exceptions, and

Not necessarily. In your case an error value may be more appropriate.
perform the temporary settings changes with RAII objects (to insure that
original settings are restored through all error paths).

Good idea.
How can I capture the error if the restore operation fails?

You cannot. An exception shall never be thrown from a destructor and a
return value is impossible. Therefore the general rule is to use only
code in the destructor that either cannot throw or that only throws an
exception that can safely be ignored. In the latter case the (ignored)
exception must be cought in the destructor (preferably with catch
(...)). Other code must be put in member functions but not in the
destuctor.
Do I simply
use uncaught_exception() in the RAII dtor to throw only if I'm not
handling an exception from the main operation?

See e.g. http://www.gotw.ca/gotw/047.htm

Best wishes,
Roland Pibinger
 
K

Kenneth Porter

IMO will be better that the system works as finite state automata, and
set the system state to "Intervention required". By means of a global
function, of by having in the RAII object a reference to the object
that maintains the state, or any other way appropriate for your
application. The general rule is to avoid to throw from destructors.

Ah, so instead of throwing the exception, the destructor could invoke an
append method on a global fault object that's monitored as part of the
background loop, and before any critical operation takes place (ie. in my
application, anything that operates a physical actuator).
 
G

Grizlyk

Kenneth said:
How can I capture the error if the restore operation fails? Do I simply
use uncaught_exception() in the RAII dtor to throw only if I'm not
handling an exception from the main operation?

I make global stack (vector) to hold errors. When your object of class
of error is created, the object makes "record" in the stack, so you can
skip double throw.

if (your_error_condition)
{
//here disable optimisation to prevent moving "tmp" to
if(!uncaught_exception()) block
//i am not shure, that it is needed
auto volatile

//here error added to you stack;
Class_of error tmp;

//here you can do not rhrow, remain old error - new error will be on
your stack also
if(!uncaught_exception())throw tmp;
}

It is not "checked by time" solution, new for me.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,764
Messages
2,569,564
Members
45,040
Latest member
papereejit

Latest Threads

Top