'Restoring a C++ stream's exception mask for caller
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
finallyI could catch an exception thrown from within in thefinallyblock 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.
Solution 1:[1]
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);
}
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | Remy Lebeau |
