Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

I want to catch an exception and bundle it within my own exception and throw upwards

Tags:

c++

exception

I have a class that manages resources. It takes a Loader class that can retrieve the resource from a path. Loader is an abstract base class, so anyone could make a new loader.

If a resource is requested that is not in the cache, the resource manager will request it from the loader. If the loader fails, resource manager throws an exception class I made called LoadingError.

I want the Loader class to throw exceptions when it fails. I can require them to inherit from my own exception base class if necessary.

The problem is, when LoadingError is thrown, I want it to include specific information about why it failed. But I don't know exactly what exceptions may be thrown by the Loader. (The user might, in his catch blocks around the resource manager).

Either I can throw only LoadingError with some general information, or allow a specific exception from Loader come out and not touch it. But I would like to catch the exception and bundle it in my own LoadingError exception, or point to it in LoadingError . But I don't know how long the pointed-to exception will last (concerned about bad pointer).

Which of the three should I do? (And how, for the third one..)

Thanks

like image 636
Neil Kirk Avatar asked Dec 09 '22 10:12

Neil Kirk


2 Answers

How about a nested exception?

try { /* ... */ }
catch (...)
{
    throw MyException("An error occurred", std::current_exception());
}

Just make a suitable class that stores the exception:

struct MyException : std::exception
{
    std::string message;
    std::exception_ptr nested_exception;

    MyException(std::string m, std::exception_ptr e)
    : message(std::move(m))
    , nested_exception(std::move(e))
    { }

    // ...
};

When the exception is caught, the catcher can rethrow the nested exception:

try { /* load resource */ }
catch (MyException & e)
{
    log("Resource loading failed: " + e.what());
    std::rethrow_exception(e.nested_exception);
}

In fact, this entire logic is already provided by the standard library via std::throw_with_nested.

like image 156
Kerrek SB Avatar answered Feb 03 '23 11:02

Kerrek SB


When one can enforce use of one's own exception classes throughout the code, the simplest way to do nested exception is undoubtedly to just define a custom exception class that contains a std::exception_ptr, which can be obtained via std::current_exception, both declared by the <exception> header.

However, C++11 supports nested exceptions via the std::nested_exception class, and functions such as std::throw_with_nested. The nested_exception constructor picks up the ? current_exception()`, if any, and stores that as its nested exception. There's no way to specify the nested exception expclitly: it's always the current exception.

This machinery also supports exception propagation through non-exception-aware code such as up through C callbacks, and it supports exceptions (and nested exceptions) of arbitrary classes, not just std::exception and derived classes.

So, when e.g. library code uses this functionality, then it's desirable to be able to deal with such standard nested exceptions, and not just one's own custom (simpler to use but less general) scheme.

Sadly, as I'm writing this (Jan 2014) the Visual C++ compiler does not yet support std::nested_exception, although it does support much of the rest of the machinery. Happily, it's not difficult to define these things. E.g., googling it, I found working code at Tomaka-17's blog, and that's the code that I've adapted below -- std::nested_exception support for Visual C++:

#include <exception>        // std::rethrow_exception

// For Visual C++ define CPPX_NORETURN as "__declspec(noreturn)"
#ifndef CPPX_NORETURN
#   define  CPPX_NORETURN [[noreturn]]
#endif

// Visual C++ 12.0 lacks these things.
// Code adapted from http://blog.tomaka17.com/2013/07/c11-nested-exceptions/.

#if !defined( GOOD_COMPILER )
#include <utility>          // std::forward
#include <type_traits>      // std::remove_reference
namespace std {
    class nested_exception
    {
    private:
        exception_ptr nested;

    public:
        CPPX_NORETURN
        void rethrow_nested() const
        { rethrow_exception(nested); }

        exception_ptr nested_ptr() const { return nested; }

        virtual ~nested_exception() {}
        nested_exception() : nested( current_exception() ) {}
    };

    template< class Type >
    CPPX_NORETURN
    void throw_with_nested( Type&& t )
    {
        typedef remove_reference<Type>::type Pure_type;

        struct Unspecified_mi_type
            : nested_exception
            , Pure_type
        {
            Unspecified_mi_type( Type&& t )
                : Pure_type( forward<Type>( t ) )
            {}
        };

        if (is_base_of<nested_exception, Pure_type>::value)
        {
            throw forward<Type>( t );
        }
        else
        {
            throw Unspecified_mi_type( forward<Type>( t ) );
        }
    }

    template< class X >
    void rethrow_if_nested( X const& x )
    {
        if( auto const ptr = dynamic_cast< nested_exception const* >( &x ) )
        {
            ptr->rethrow_nested();  // It's specified to do this, C++11 §18.8/8.
        }
    }
}
#endif // not GOOD_COMPILER

With g++ 4.7.2 it would be more involved to define this stuff, but since g++ 4.8.2 already has it it's not necessary: for g++ just upgrade the compiler if necessary.

Then the next problem is how to retrieve the nested exception information.

Essentially that boils down to iteratively rethrowing and catching each nested exception, e.g. as follows:

#include <iostream>
#include <stdexcept>        // std::runtime_error
#include <stdlib.h>         // EXIT_FAILURE, EXIT_SUCCESS

struct Loader { virtual void load() = 0; };

struct Resource_manager
{
    Loader&     loader;

    void foo()
    {
        try
        {
            loader.load();
        }
        catch( ... )
        {
            std::throw_with_nested( std::runtime_error( "Resource_manager::foo failed" ) );
        }
    }

    Resource_manager( Loader& a_loader )
        : loader( a_loader )
    {}
};

int main()
{
    using std::cerr; using std::endl;

    struct Failing_loader
        : Loader
    {
        virtual void load() override { throw std::runtime_error( "Loading failed"); }
    };

    try
    {
        Failing_loader          loader; 
        Resource_manager        rm( loader );

        rm.foo();
        return EXIT_SUCCESS;
    }
    catch( ... )
    {
        bool is_cause = false;
        for( auto px = std::current_exception();  px != nullptr;  )
        {
            try
            {
                std::rethrow_exception( px );
            }
            catch( std::exception const& x )
            {
                cerr << "!" << (is_cause? "<because> " : "") << x.what() << endl;
                px = nullptr;
                if( auto pnx = dynamic_cast< std::nested_exception const* >( &x ) )
                {
                    px = pnx->nested_ptr();
                }
            }
            catch( ... )
            {
                cerr << (is_cause? "!<because of an " : "!<") << "unknown failure>" << endl;
                px = nullptr;
            }
            is_cause = true;
        }
    }
    return EXIT_FAILURE;
}

Output:

!Resource_manager::foo failed
!<because> Loading failed

Disclaimer: I cooked up the above code for this answer, so it's not been extensively tested. But anyway, enjoy!

like image 25
Cheers and hth. - Alf Avatar answered Feb 03 '23 11:02

Cheers and hth. - Alf