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:
- How do I write code that uses a stream supplied by a caller, knowing that the stream could have totally different behavior depending on what the exception mask is?
- How do you write cleanup code that could throw? If I was using a
finally
I could catch an exception thrown from within in thefinally
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.
CodePudding user response:
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);
}