为调用者恢复 C++ 流的异常掩码
Restoring a C++ stream's exception mask for caller
我正在编写一个 C++ 函数,它将 std::istream
作为参数并从中读取以解码图像。解码图像时,如果在读取过程中发生某些错误,我希望流抛出异常。这样,我就不必在解码逻辑的其余部分穿插检查错误标志,从而使我的代码更简单。
因为调用者可能会在没有启用异常的情况下传递流,所以我的函数将启用它们。我想在函数退出时恢复流的初始异常掩码。我只是在学习 C++,并试图用惯用的方式来处理这样的情况——我通常会在 finally
块中抛出清理代码:
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);
}
}
我看到有人说 finally
类型的清理代码应该像 RAII 一样处理。我想我可以创建一个 class 来包装这个责任,它保存了一个指向流的指针和原始异常掩码。它的析构函数会将原始异常掩码恢复到流中。
// Something like this...
StreamExceptionMaskMemo::StreamExceptionMaskMemo(std::istream* stream)
{
this->originalExceptionSettings = stream->exceptions();
this->stream = stream;
}
StreamExceptionMaskMemo::~StreamExceptionMaskMemo()
{
// Could throw!
this->stream->exceptions(this->originalExceptionSettings);
}
虽然这对我的情况有效,但使用此 class 存储异常掩码 指定应抛出异常 以用于禁用 异常的函数。如果 class 以这种方式使用,析构函数将重新启用异常,这会立即抛出当前状态暗示的任何异常。
我意识到这有点做作 - 调用者可能不会再使用混乱的流 - 但我仍然对如何解决以下问题感到困惑:
- 如何编写使用调用者提供的流的代码,知道该流可能具有完全不同的行为,具体取决于异常掩码是什么?
- 如何编写可能抛出的清理代码?如果我使用
finally
我可以捕获从 finally
块中抛出的异常并做一些适当的事情。但是,如果我将清理责任推给另一个 class 以允许 RAII,我必须要么让它的析构函数抛出异常,要么吞下它们。
如果调用者不想要流异常,旧掩码将不会启用异常,所以旧掩码的恢复不会抛出任何异常。没问题。
如果调用者确实想要流异常,那么如果流状态与调用者想要的异常相匹配,恢复将抛出异常。同样,这不是问题。
因此,唯一真正的问题是可能从 RAII 析构函数内部抛出异常,这通常非常糟糕(但 小心)。
在这种情况下,我建议不要使用 RAII。一个简单的 try/catch
就足够了(try/finally
不可移植),例如:
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);
}
我正在编写一个 C++ 函数,它将 std::istream
作为参数并从中读取以解码图像。解码图像时,如果在读取过程中发生某些错误,我希望流抛出异常。这样,我就不必在解码逻辑的其余部分穿插检查错误标志,从而使我的代码更简单。
因为调用者可能会在没有启用异常的情况下传递流,所以我的函数将启用它们。我想在函数退出时恢复流的初始异常掩码。我只是在学习 C++,并试图用惯用的方式来处理这样的情况——我通常会在 finally
块中抛出清理代码:
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);
}
}
我看到有人说 finally
类型的清理代码应该像 RAII 一样处理。我想我可以创建一个 class 来包装这个责任,它保存了一个指向流的指针和原始异常掩码。它的析构函数会将原始异常掩码恢复到流中。
// Something like this...
StreamExceptionMaskMemo::StreamExceptionMaskMemo(std::istream* stream)
{
this->originalExceptionSettings = stream->exceptions();
this->stream = stream;
}
StreamExceptionMaskMemo::~StreamExceptionMaskMemo()
{
// Could throw!
this->stream->exceptions(this->originalExceptionSettings);
}
虽然这对我的情况有效,但使用此 class 存储异常掩码 指定应抛出异常 以用于禁用 异常的函数。如果 class 以这种方式使用,析构函数将重新启用异常,这会立即抛出当前状态暗示的任何异常。
我意识到这有点做作 - 调用者可能不会再使用混乱的流 - 但我仍然对如何解决以下问题感到困惑:
- 如何编写使用调用者提供的流的代码,知道该流可能具有完全不同的行为,具体取决于异常掩码是什么?
- 如何编写可能抛出的清理代码?如果我使用
finally
我可以捕获从finally
块中抛出的异常并做一些适当的事情。但是,如果我将清理责任推给另一个 class 以允许 RAII,我必须要么让它的析构函数抛出异常,要么吞下它们。
如果调用者不想要流异常,旧掩码将不会启用异常,所以旧掩码的恢复不会抛出任何异常。没问题。
如果调用者确实想要流异常,那么如果流状态与调用者想要的异常相匹配,恢复将抛出异常。同样,这不是问题。
因此,唯一真正的问题是可能从 RAII 析构函数内部抛出异常,这通常非常糟糕(但
在这种情况下,我建议不要使用 RAII。一个简单的 try/catch
就足够了(try/finally
不可移植),例如:
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);
}