Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Scope(failure) in C++11?

I wrote a very simple solution however someone laughed and found a flaw as shown here 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;
}

In short the problem is my code looks at std::uncaught_exception(). When an exception is thrown and a normal destructor is executed. If i use scope failure there it will look at std::uncaught_exception() and think the object scope is lost due to exception rather then simply walking out of scope.

I can't think of any good solutions to differentiate leaving scope normally VS having an exception thrown IN it. Yes i know throwing is a bad idea in dtors BUT thats why I fail to notice this problem, because I never throw in exceptions.

How do I differentiate/solve this?


2 Answers

No exception was thrown but it thinks it has.

An exception was thrown, just not from right there.

There is no mechanism in C++11 to ask, "Was an exception thrown from code just below me, but not from code elsewhere in the call-stack?" std::uncaught_exception is doing exactly what it is supposed to do: say whether there is an exception currently in the process of being resolved at the time the function is called. And there is, so it returns true.

C++17 adds std::uncaught_exceptions (note the plural), which can be used to detect the difference. With such a tool, you can make your FailBlock object work:

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() returns the number of exceptions that are provoking stack unwinding at the time the call was made. If the number is the same during the constructor and destructor of an object (assuming it's a stack object), then the destructor is not being called due to an exception being thrown through where this type was used.

But without this tool, it, there's not much you can do to differentiate between an exception provoking the exiting of the scope rather than exiting a scope when exception unwinding just happens to be going on. So you're going to have to bite the bullet and catch the exception like everyone else.

Or just don't put this FailBlock thing in destructors. It seems to me that those should go directly into regular functions that can actually throw (and destructors should never throw). It seems to me that you're worried about a corner case that doesn't make any real sense.

like image 63
Nicol Bolas Avatar answered Dec 01 '25 02:12

Nicol Bolas


I can't think of any good solutions to differentiate leaving scope normally VS having an exception thrown IN it.

Check stack_unwinding library - I have implemented scope(failure) and scope(success) features in C++.

It is based on platform specific function uncaught_exception_count. It is similar to std::uncaught_exception from standard library, but instead of boolean result it returns unsigned int showing current count of uncaught exceptions.

Currently it is tested on {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}.

In C++11 folowing syntax is 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;
    };
}

In C++98 it is a bit more 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
}

Also, library has UNWINDING_AWARE_DESTRUCTOR feature. Example:

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

However, there are some cases where UNWINDING_AWARE_DESTRUCTOR may give wrong results (though scope(success) and scope(failure) features are not affected by such issues).

like image 30
Evgeny Panasyuk Avatar answered Dec 01 '25 03:12

Evgeny Panasyuk



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!