【问题标题】:Scope(failure) in C++11?C ++ 11中的范围(失败)?
【发布时间】:2012-10-11 16:39:07
【问题描述】:

我写了一个非常简单的解决方案,但是有人笑了,发现了一个缺陷,如下所示http://ideone.com/IcWMEf

#include <iostream>
#include <ostream>
#include <functional>
#include <exception>
using namespace std;

// Wrong scope(failure)
class FailBlockT
{
    typedef function<void()> T;
    public:
    T t;
    FailBlockT(T t)
    {
        this->t=t;
    }
    ~FailBlockT()
    {
        if (std::uncaught_exception())
        {
            t();
        }
    }
};

struct Test
{
    ~Test()
    {
        try
        {
            FailBlockT f([]()
            {
                cout << "failure" << endl;
            });
            // there is no any exception here, but "failure" is printed.
            // See output below
        }
        catch(...)
        {
            cout << "some exception" << endl;
        }
    }
};

int main()
{
    try
    {
        Test t;
        throw 1;
    }
    catch(int){}
    return 0;
}

简而言之,问题是我的代码查看std::uncaught_exception()。当抛出异常并执行正常的析构函数时。如果我在那里使用范围故障,它会查看 std::uncaught_exception() 并认为对象范围由于异常而丢失,而不是简单地走出范围。

我想不出任何好的解决方案来区分正常离开范围与在其中抛出异常。是的,我知道在 dtors 中抛出是一个坏主意,但这就是为什么我没有注意到这个问题,因为我从不抛出异常。

我如何区分/解决这个问题?

【问题讨论】:

  • 目前还不清楚您到底想要完成什么。
  • @sth 我加粗了,有帮助吗?
  • @acidzombie24:好的,我们试试这个方法。 为什么您要“区分正常离开范围与在其中抛出异常”?更具体地说,为什么你需要在析构函数中这样做,唯一的目的应该是在对象之后进行清理?为什么析构函数需要关心它是否由于异常而被清理?
  • 另外,什么是“范围故障”?
  • @NicolBolas 这个问题的答案解释了 Scope(failure) {...} 是什么 stackoverflow.com/questions/1247778/… 并且在演示中喜欢 37-39 显示了问题。没有抛出异常,但它认为它有。

标签: c++ exception c++11 destructor


【解决方案1】:

我想不出任何好的解决方案来区分正常离开范围与在其中抛出异常。

检查stack_unwinding library - 我已经在 C++ 中实现了范围(失败)和范围(成功)功能。

它基于平台特定的函数 uncaught_exception_count。它类似于标准库中的 std::uncaught_exception,但它不是返回布尔结果,而是返回 unsigned int 显示当前未捕获异常的计数。

目前它在 {Clang 3.2, GCC 3.4.6, GCC 4.1.2, GCC 4.4.6, GCC 4.4.7, MSVC2005SP1, MSVC2008SP1, MSVC2010SP1, MSVC2012} x {x32, x64} 上进行测试。

在 C++11 中,以下语法是 available:

try
{
    int some_var=1;
    cout << "Case #1: stack unwinding" << endl;
    scope(exit)
    {
        cout << "exit " << some_var << endl;
        ++some_var;
    };
    scope(failure)
    {
        cout << "failure " << some_var << endl;
        ++some_var;
    };
    scope(success)
    {
        cout << "success " << some_var << endl;
        ++some_var;
    };
    throw 1;
} catch(int){}
{
    int some_var=1;
    cout << "Case #2: normal exit" << endl;
    scope(exit)
    {
        cout << "exit " << some_var << endl;
        ++some_var;
    };
    scope(failure)
    {
        cout << "failure " << some_var << endl;
        ++some_var;
    };
    scope(success)
    {
        cout << "success " << some_var << endl;
        ++some_var;
    };
}

在 C++98 中更多的是noisier:

try
{
    cout << "Case #1: stack unwinding" << endl;
    BOOST_SCOPE_EXIT(void) { cout << "exit" << endl; } BOOST_SCOPE_EXIT_END
    SCOPE_FAILURE(void) { cout << "failure" << endl; } SCOPE_FAILURE_END
    SCOPE_SUCCESS(void) { cout << "success" << endl; } SCOPE_SUCCESS_END
    throw 1;
} catch(int){}
{
    cout << "Case #2: normal exit" << endl;
    BOOST_SCOPE_EXIT(void) { cout << "exit" << endl; } BOOST_SCOPE_EXIT_END
    SCOPE_FAILURE(void) { cout << "failure" << endl; } SCOPE_FAILURE_END
    SCOPE_SUCCESS(void) { cout << "success" << endl; } SCOPE_SUCCESS_END
}

此外,库具有UNWINDING_AWARE_DESTRUCTOR 功能。 Example:

struct DestructorInClass
{
    UNWINDING_AWARE_DESTRUCTOR(DestructorInClass,unwinding)
    {
        cout << "DestructorInClass, unwinding: "
             << ( unwinding ? "true" : "false" ) << endl;
    }
};

但是,在某些情况下,UNWINDING_AWARE_DESTRUCTOR 可能会give wrong results(尽管范围(成功)和范围(失败)功能不受此类问题的影响)。

【讨论】:

    【解决方案2】:

    没有抛出异常,但它认为有。

    抛出了一个异常,只是不是从那里

    C++11 中没有机制可以询问“是否从我下面的代码中抛出了异常,但 不是 来自调用堆栈中其他地方的代码?” std::uncaught_exception 正在做它应该做的事情:说明在调用函数时当前是否存在正在解决的异常。有,所以它返回true

    C++17 增加了std::uncaught_exceptions(注意复数),可以用来检测差异。使用这样的工具,您可以使您的 FailBlock 对象工作:

    template<typename Func>
    class FailBlockT
    {
    private:
        int e_count_;
        T t_;
    
    public:
        FailBlockT(T t) : e_count_(std::uncaught_exceptions()), t_(t) {}
    
        FailBlock(const FailBlock &) = delete; //The type should not be mobile.
    
        ~FailBlockT()
        {
            if (std::uncaught_exceptions() != e_count_)
            {
                t_();
            }
        }
    };
    

    std::uncaught_exceptions() 返回调用时引发堆栈展开的异常数。如果在对象的构造函数和析构函数期间编号相同(假设它是堆栈对象),则由于使用此类型的位置抛出异常,因此不会调用析构函数。

    但是,如果没有这个工具,您就无法区分引发范围退出的异常而不是在恰好发生异常展开时退出范围。因此,您将不得不像其他人一样硬着头皮捕捉异常。

    或者只是不要把这个FailBlock 东西放在析构函数中。在我看来,那些应该直接进入可以实际抛出的常规函数​​(而析构函数永远不应该抛出)。在我看来,您担心的是一个没有任何实际意义的极端案例。

    【讨论】:

    • 我明白了,它假设模仿 D 特性,假设摆脱捕获。但我明白了。写 catch 会很烦人,但在失败的情况下我必须这样做。我正在考虑作弊,还好这没有在生产代码中使用。
    • @acidzombie24: scope(failure) 不会摆脱 catch。它使catch 用于捕获 异常,而不是暂时停止异常传播以进行一些清理。你知道,这是实际工作。它摆脱了捕获和释放的废话。另外,我刚刚说过FailBlock 完全按照您的要求工作,只要您不在对象的析构函数中使用它(或作为对象的成员,就此而言)。所以这取决于您:99% 的时间避免使用 catch 语句,或者始终编写它。
    • 我的 hack 解决方案几乎是在对象构造中使用对 uncaught_exception 的断言。但就像我说的,我个人不会在 dtors 中编写 throwing 代码。
    • 为了区分正常退出和异常退出,你可以在最后一行添加一个guard.dismiss()。如果它被执行,你的作用域就会正常退出。如果没有,你得到一个例外(或提前返回)。
    • 这个答案已经很老了,但是 [...] 只是不要把这个 FailBlock 放在析构函数中 是不够的。析构函数可能会在无意中调用使用 FailBlock 的函数进行清理,该函数现在已被传递破坏。它背后的动机是好的,使用 C++17 std::uncaught_exceptions() 现在可以正确地做到这一点。有关该概念的实现,请参阅 folly
    猜你喜欢
    • 2012-02-18
    • 2017-08-11
    • 1970-01-01
    • 1970-01-01
    • 2016-05-31
    • 2021-12-17
    • 1970-01-01
    • 1970-01-01
    • 2011-08-08
    相关资源
    最近更新 更多