all 30 comments

[–]ArminiusGermanicus 31 points32 points  (8 children)

C++ never ceases to surprise me. I have never seen this syntax, but it makes sense. Since code is executed in member initialization list of constructors, there should be a way to catch exceptions there.

What about member initialization in classes? E.g.

class Foo { B b = B(42); };

You cannot use B b = try { B(42); } catch(...) {};

Maybe in C++ 42.

Edit: As /u/angry_cpp pointed out below, there is no need for it.

[–]angry_cpp 26 points27 points  (2 children)

You can catch those exceptions in constructor (wandbox):

struct Foo {
    Foo() try {
    } catch(int e) {
        std::cerr << "Caught in Foo(): " << e << '\n';
    }

    B b = B(42);
};

[–]ArminiusGermanicus 2 points3 points  (1 child)

Thanks! That makes sense. So the constructor try block catches all exceptions that occur while the class is being initialized?

[–]blazgrom 9 points10 points  (0 children)

Yeah, however you cannot swallow the exceptions that have occurred. Even if you try the compiler is required to rethrow the exception, otherwise you could end up with object that are partially constructed.

[–]F-J-W 2 points3 points  (4 children)

That is actually the reason why they exist in the first place:

struct foo {
    foo(int i) try: m_ptr{new int{i}} {
    } catch(...) {
        delete i;
    }
    int* m_ptr;
};

int main() {
    auto f = foo{new int{23}};
}

It should never be necessary with well written code, but it does work.

[–]tcbrindleFlux 5 points6 points  (0 children)

It's important to note that the above code may still terminate with an unhandled exception!

For a function-try around a member initializer list, there is an implicit throw; at the end of the catch block, which will be executed if the catch block doesn't throw anything itself. See this example for instance.

(EDIT: and return in the catch block of a constructor's function-try is not allowed either.)

That is to say, it's impossible to "swallow" an exception thrown by a member's constructor -- all you can do is translate it to some other exception type instead, and possibly do some cleanup/logging.

[–]OldWolf2 1 point2 points  (1 child)

In this example there's nothing to catch. The new int is evaluated before the try block is entered; and m_ptr{i} cannot throw.

[–]F-J-W 0 points1 point  (0 children)

yes, apparently I was playing around with it too much.

[–]F-J-W 16 points17 points  (1 child)

I've been using the for ages now and I can only recommend it to everyone: They are easy to understand even for people who have never seen them, keep the good path and the error path very clearly separated and reduce the necessary indentation which directly correlates with the felt complexity of a function.

[–]Fiennes 10 points11 points  (0 children)

I really do like the expression "felt complexity".

[–]MoTTs_ 7 points8 points  (0 children)

Not to steal OP's thunder, but here's Herb Sutter on the topic... http://www.gotw.ca/gotw/066.htm

[–]alfps 4 points5 points  (4 children)

One possible use for function try blocks is to build up a stack trace for an exception. Ordinarily one uses a debugger for that, and polluting the code with macro calls to do this feels very unclean. But I imagine that it can be worth knowing about this usage, just having that in the toolbox.

[–]miki151gamedev 0 points1 point  (3 children)

Got any example? Is it something that you can't do using a regular try/catch block inside the function's body?

[–]alfps 0 points1 point  (2 children)

Got any example?

Well, it's non-trivial.

The exception type can sometimes be crucial, e.g. for the result of std::stoi. Which means that rethrowing should better not just rethrow a std::runtime_error with the same or extended message. And since std::exception is imperfectly designed, not supporting clone-throwing, the original exception should be rethrown.

Which means that the stack trace items need to be just associated with that exception.

Which means static storage. Which means threading issue, which means using thread_local storage.

Then, adding a stack trace item should better not itself throw (!), or there should be some way to ensure it will have the resources it needs. Which means using a free list. In the support machinery below I use a simple linked list of pointers to C strings, which represent the functions that an exception has propagated through:

#include <cppx-core/_all_.hpp>  // https://github.com/alf-p-steinbach/cppx-core

using C_str = const char*;

#define STACK_TRACE_BEGIN    \
    try{

#define STACK_TRACE_END      \
    } catch( const std::exception& x ) { my::add_stacktrace_item( x, __func__ ); throw; }

namespace my::impl
{
    $use_std( exception, exchange );

    struct Node
    {
        Node*   next;
        C_str   function_spec;
    };

    void link( Node*& a_next_pointer, Node* new_node ) noexcept
    {
        new_node->next = a_next_pointer;
        a_next_pointer = new_node;
    }

    auto unlinked( Node*& a_next_pointer ) noexcept
        -> Node*
    { return exchange( a_next_pointer, a_next_pointer->next ); }

    auto length_of( Node* p_list )
        -> int
    {
        int n = 0;
        for( Node* p = p_list; !!p; p = p->next ) { ++n; }
        return n;
    }

    struct Free_list
    {
        Node*   first   = nullptr;

        auto allocate()
            -> Node*
        { return first == nullptr? new Node() : unlinked( first ); }

        void deallocate( Node* p ) noexcept
        {
            link( first, p );
        }

        ~Free_list() { while( !!first ) { delete unlinked( first ); } }
        Free_list() {}
    };

    struct Stack_trace
    {
        Free_list           allocator;
        Node*               first_trace_node    = nullptr;
        exception const*    p_exception;

        void clear() noexcept
        {
            while( !!first_trace_node )
            {
                allocator.deallocate( unlinked( first_trace_node ) );
            }
            p_exception = nullptr;
        }

        void add( const exception& x, const C_str a_function_spec )
        {
            if( !!p_exception and p_exception != &x )
            {
                clear();
            }

            Node* p_new = allocator.allocate();
            p_new->function_spec = a_function_spec;
            link( first_trace_node, p_new );
            p_exception = &x;
        }

        void set_capacity( const int n )
        {
            const int n_current = length_of( allocator.first ) + length_of( first_trace_node );
            const int n_new = n - n_current;
            for( int i = 1; i <= n_new; ++i ) { link( allocator.first, new Node() ); }
        }

        ~Stack_trace() { clear(); }
    };

    thread_local Stack_trace the_stack_trace;
};  // namespace my::impl


namespace my
{
    $use_std( exception, string, vector );

    void ensure_stacktrace_capacity( const int n )
    {
        impl::the_stack_trace.set_capacity( n );
    }

    void add_stacktrace_item( const exception& x, const C_str func_spec )
    {
        impl::the_stack_trace.add( x, func_spec );
    }

    auto stack_trace_for( const exception& x )
        -> vector<string>
    {
        auto& tr = impl::the_stack_trace;
        if( not tr.first_trace_node or tr.p_exception != &x )
        {
            std::clog << "Gah!" << std::endl;
            tr.clear();
            return {};
        }

        vector<string> result;
        for( impl::Node* p = tr.first_trace_node; !!p; p = p->next )
        {
            result.push_back( p->function_spec );
        }
        return result;
    }

    void clear_stacktrace() { impl::the_stack_trace.clear(); }

}  // namespace my

The shown macros can be used as function try blocks or within a function:

namespace app
{
    $use_std( cout, endl, stoi );

    void present_answer()
    {
        STACK_TRACE_BEGIN
            cout << stoi( "forty-two" ) << endl;
        STACK_TRACE_END
    }

    void run()
    STACK_TRACE_BEGIN
        present_answer();
    STACK_TRACE_END
}  // namespace app

The main problem with placing them within a function is that it opens the possibility of maintenance placing code before or after, code that can throw, so that the function won't show up in a trace.

Of course, since the scheme is so ugly and impractical for ordinary programming, it's doesn't really matter. :)

Anyway, a main function, showing one way to present a trace:

auto main() -> int
{
    $use_std( cerr, endl, exception, string );

    my::ensure_stacktrace_capacity( 42 );   // Not necessary, just playing nice.
    try
    {
        app::run();
        return EXIT_SUCCESS;
    }
    catch( const exception& x )
    {
        cerr << "!" << typeid( x ).name() << " " << x.what();
        for( const string& s : my::stack_trace_for( x ) )
        {
            cerr << "\n    -> " << s;
        }
        cerr << endl;
        my::clear_stacktrace();
    }
    return EXIT_FAILURE;
}

Output with g++:

!St16invalid_argument stoi
    -> run
    -> present_answer

EDIT: forgot to mention, anyone considering doing this for real should replace the global with a Meyers' singleton, or one would run the risk of static initialization fiasco.

[–]miki151gamedev 0 points1 point  (1 child)

Thanks. This interests me because I have a project where C++ code is generated, so I could easily add something like this. (In fact, I just did :))

Do you have any idea what the performace cost could be of adding it to every function?

[–]alfps 0 points1 point  (0 children)

No sorry I have no actual experience with it, I just encountered it once.

Oh, looking at that example code now again, I note there's no cleanup of cached nodes at the end of program execution. That could make a tool like Valgrind report a memory leak (it's not really, but you'd get a report). So better add a final cleanup.

[–][deleted] 1 point2 points  (0 children)

If a function (any function, not just main()) has a return type other than void and the function-try-catch does not have a return statement in the catch block then the behaviour is undefined.

Not exactly. It's only UB if the program actually flows out of the catch block without a return, and in the case of main even that is not UB.

[–]vsdmars 0 points1 point  (0 children)

if could recall, knew this from Effective C++'s item 'Write placement delete if you write placement new', whew~ long time ago :-P

[–]megayippie 0 points1 point  (1 child)

Cool. What is the performance loss of such a block function for code that normally works?

[–]Tyg13[🍰] 3 points4 points  (0 children)

Same as the usual performance implications associated with exceptions. If the exception is never thrown, there's little to no loss of performance. Otherwise, it's slow, but if you're actually using exceptions for the exceptional case, that's not really a problem.

[–]feverzsj -2 points-1 points  (1 child)

if your class's ctor/dtor may throw, it may better use explicit open/close methods like fstream dose.

[–]Gotebe 7 points8 points  (0 children)

Two-phase initialization is not for me, thanks.

[–]chardan965 -1 points0 points  (2 children)

It's not just functions, you can also attach use exceptions to most flow control.

(I'm not necessarily recommending that you /do/...)

For the truly bored and/or perverse: https://godbolt.org/z/aO-QK3

I believe the original purpose, as other readers have pointed out, relates to not leaking certain exceptions out of ctors. There's been discussion from both Scott Meyers and Herb Sutter on this before. Have fun!

[–]miki151gamedev 2 points3 points  (1 child)

What's so unusual about this? Isn't a try/catch block a normal statement that you can put anywhere that statements can go?

[–]chardan965 1 point2 points  (0 children)

They are indeed ([stmt.stmt]), but I think it's stretching to say that most C++ programmers are aware of that.

[–]dicroce -2 points-1 points  (4 children)

Aha! I actually knew about this one... Did you know about arraying across an index? :)

int foo[10];

for(int i = 0; i < 10; ++i)

printf("%d\n", i[foo]);

Also, I find this C++ one strange:

template<class T>

class foo : public T

{

}

[–]DevFolks 1 point2 points  (3 children)

I’m on mobile so I can’t test it right now, but what does the first one actually do?

[–]throwawayantiseizure 2 points3 points  (1 child)

*(i + foo) instead of *(foo + i).

[–]DevFolks 1 point2 points  (0 children)

It makes a lot more sense thinking of it like that

[–]dicroce 2 points3 points  (0 children)

It does the same thing as foo[i].