还原C++流式呼叫者的异常蒙版

发布于 2025-01-21 14:08:17 字数 1783 浏览 2 评论 0原文

我正在编写一个C ++函数,该功能以std :: istream为参数,并从中读取以解码图像。解码图像时,如果在阅读过程中出现某些错误,我希望流进行异常。这样,我就不必用我的解码逻辑的其余部分插入错误标志,从而使我的代码变得更加简单。

由于呼叫者可能会通过流启用流,因此我的功能将启用它们。当功能退出时,我想还原流的初始异常蒙版。我只是在学习C ++,并试图将我的头缠绕在处理这样的情况的惯用方式上 - 我通常会在最后将清理代码扔进终于块:

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);
        }
}

我已经看到人们说<代码>最后,应像RAII一样处理 -Type清理代码。我想我可以创建一个小班来包装这一责任,从而节省了指向流的指针和原始异常面具。它的破坏者将将原始异常面膜恢复到流中。

// Something like this...
StreamExceptionMaskMemo::StreamExceptionMaskMemo(std::istream* stream)
{
        this->originalExceptionSettings = stream->exceptions();
        this->stream = stream;
}

StreamExceptionMaskMemo::~StreamExceptionMaskMemo()
{
        // Could throw!
        this->stream->exceptions(this->originalExceptionSettings);
}

虽然在我的情况下这将有效,但使用此类存储一个异常掩码,该类别应抛出,以便在 disables exceptions中使用。如果使用该类别,则可以重新确定异常,这立即引发了当前状态所暗示的任何例外。

我意识到这有点人为 - 也许呼叫者不再使用混乱的流 - 但是我仍然对如何解决以下问题感到困惑:

  1. 我如何编写使用呼叫者提供的流的代码,知道该流可以完全不同的行为,具体取决于例外面具是什么?
  2. 您如何编写可以投掷的清理代码?如果我使用的是最后,我可以在终于 block中捕获一个异常,然后做适当的事情。但是,如果我要把清理责任拆分到另一个班级以允许RAII,我必须让它的destuructor抛出例外,或者吞下它们。

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:

  1. 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?
  2. How do you write cleanup code that could throw? If I was using a 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.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

得不到的就毁灭 2025-01-28 14:08:17

如果呼叫者不想要流异常,则旧面具将无法启用异常,因此旧面具的恢复不会抛出任何例外。没问题。

如果呼叫者 die 想要流异常,则如果流状态匹配呼叫者想要的例外,则修复将引发异常。再次,这不是问题。

因此,唯一真正的问题是可能会从RAII灾难内抛出例外,这通常非常糟糕(但是可以完成 a>小心)。

在这种情况下,我建议不要使用RAII。一个简单的尝试/捕获就足够了(尝试/最后是不动的),例如:

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);
}

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);
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文