I am writing a C++ function that takes a std::istream
as an argument and reads from it to decode an image. When decoding the image, I want the stream to throw exceptions if some error occurs during reading. That way, I don't have to intersperse checking the error flags with the rest of my decoding logic, making my code simpler.
Because the caller might pass a stream without exceptions enabled, my function will enable them. I would like to restore the stream's initial exception mask when the function exits. I am just learning C++ and am trying to wrap my head around the idiomatic way to handle situations like this - where I'd normally toss cleanup code in a finally
block:
Image::Image(std::istream& stream) {
std::ios_base::iostate originalExceptionSettings = stream.exceptions();
try {
// Enable exceptions.
stream.exceptions(
std::ios_base::badbit |
std::ios_base::failbit |
std::ios_base::eofbit);
// Do some stuff that might cause stream to throw...
}
finally { // If only!
// Restore settings that the caller expects.
stream.exceptions(originalExceptionSettings);
}
}
I've seen people say that finally
-type cleanup code should be handled like RAII. I guess I could create a little class to wrap this responsibility, which saves a pointer to the stream and the original exception mask. Its destructor would restore the original exception mask to the stream.
// Something like this...
StreamExceptionMaskMemo::StreamExceptionMaskMemo(std::istream* stream)
{
this->originalExceptionSettings = stream->exceptions();
this->stream = stream;
}
StreamExceptionMaskMemo::~StreamExceptionMaskMemo()
{
// Could throw!
this->stream->exceptions(this->originalExceptionSettings);
}
While that will work in my case, it would be problematic to use this class to store an exception mask that specifies exceptions should be thrown for use in a function that disables exceptions. If the class was used that way, the destructor would reenable the exceptions, which immediately throws whatever exceptions are implied by the current state.
I realize this is a bit contrived - probably the caller won't use a messed up stream anymore - but I am still confused about how I'd address the following problems:
finally
I could catch an exception thrown from within in the finally
block and do something appropriate. But if I am spinning off the responsibility for cleanup to another class to allow RAII, I have to either let its destructor throw exceptions, or swallow them.If the caller doesn't want stream exceptions, the old mask will not enable exceptions, so the restoration of the old mask will not throw any exception. Not a problem.
If the caller does want stream exceptions, then the restoration will throw an exception if the stream state matches what the caller wants an exception for. Again, not a problem.
So, the only real problem is the possibility of throwing an exception from inside an RAII destructor, which is generally very bad (but can be done with care).
In this case, I would suggest just not using RAII. A simple try/catch
will suffice (try/finally
is not portable), eg:
Image::Image(std::istream& stream) {
std::ios_base::iostate originalExceptionSettings = stream.exceptions();
try {
// Enable exceptions (may throw immediately!)
stream.exceptions(
std::ios_base::badbit |
std::ios_base::failbit |
std::ios_base::eofbit);
// Do some stuff that might cause stream to throw...
}
catch (const std::exception &ex) {
// error handling as needed...
// if not a stream error, restore settings that
// the caller expects, then re-throw the original error
if (!dynamic_cast<const std::ios_base::failure*>(&ex))
{
stream.exceptions(originalExceptionSettings);
throw;
}
}
catch (...) {
// error handling as needed...
// Unknown error but not a stream error, restore settings
// that the caller expects, then re-throw the original error
stream.exceptions(originalExceptionSettings);
throw;
}
// restore settings that the caller expects (may throw what caller wants!)
stream.exceptions(originalExceptionSettings);
}