Is it possible to write/wrap the exception handlin

2019-04-07 19:02发布

This is about wrapping the exception handling logic in some sort of class. While writing c++ code, many time we need to catch many type/variants of exception depending on what client throw. This lead us to write similar types of code(many times) in catch() clause.

In below sample example, I have written the function(), which can throw exception in the many possible form.

I wanted to know is it possible to write/wrap such logic in the form of class so that end user would have to write similar types of code at once place?. Does it make any sense or it has any meaning?

#include<vector>
#include<string>
#include<exception>
#include<iostream>

// this function can throw std::exception, std::string, int or unhandled
void function() {
  std::vector<int> x{1,2,3,4,5};
  auto val = x.at(x.size()); //throw out-of-range error
}

int main() {
try { function(); } 
catch(std::exception& e) { std::cout<<e.what()<<std::endl; }
catch(std::string& msg)  { std::cout<<msg<<std::endl; }
catch(int i)         { std::cout<<i<<std::endl; }
catch(...)       { std::cout<<"Unhandled Exception"<<std::endl; }
return 0;
}

So far I thought in this way and below is the pseudo logic.

class exceptionwrapper{
exceptionwrapper(function pointer* fp) {
 // functions which would be executing inside try
}
~exceptionwrapper() {
// all catch() clause can be written over here
// or some other member function of this class
}

};

The object of this class can be instantiated in the main() in this way.

int main() {
 exceptionwrapper obj(function);
 //here execptionwrapper destructor would take care about calling all type of catch
}

2条回答
我欲成王,谁敢阻挡
2楼-- · 2019-04-07 19:07

What you're asking for is possible, but I don't think it's very useful. First let's implement a mechanism to accept a callable object, and its associated arguments, which we'll invoke in the destructor of exception_wrapper.

template<typename Func, typename... Args>
struct exception_wrapper
{
    exception_wrapper(Func f, Args... args) 
    : f_(std::move(f))
    , args_(std::make_tuple(std::move(args)...))
    {}

    ~exception_wrapper() 
    {
        try {
            invoke();
        } catch(std::exception const& e) {
            std::cerr << "Caught exception: " << e.what() << std::endl;
        } catch(...) {
            std::cerr << "Caught unknown exception" << std::endl;
        }
    }

    template<std::size_t... I>
    void apply(std::index_sequence<I...>)
    {
        f_(std::get<I>(args_)...);
    }

    void invoke()
    {
        apply(std::index_sequence_for<Args...>());
    }

    Func f_;
    std::tuple<Args...> args_;
};

template<typename Func, typename... Args>
auto make_exception_wrapper(Func&& f, Args&&... args)
{
    return exception_wrapper<Func, Args...>(
        std::forward<Func>(f), std::forward<Args>(args)...);
}

This makes use of the C++14 std::integer_sequence; if that's not available on your implementation there are several answers on SO that show how to implement it yourself (this one for instance).

To use it, create an exception_wrapper object, and your function will be invoked when the destructor executes.

make_exception_wrapper(function);

Live demo


Now, I don't think this is useful because in general you should only catch exceptions if your code is able to handle them, and continue operating normally. Otherwise let them propagate to the top level where you might want to install a handler so it allows you to exit the program gracefully.

Given that, it's unlikely that there'll be a common approach to handling all exceptions thrown by your code, which greatly reduces the utility of exception_wrapper as implemented. You could modify it to take another callable argument, the exception handler that will be passed the std::exception object that was caught, which makes the class a little more generic.

Additionally, invoking the function in the destructor means you cannot pass the return value, if any, back to the caller. This can be fixed by invoking the function within exception_wrapper::operator() instead, but that then adds the wrinkle of what to return in the case an exception is indeed thrown, and you've suppressed it.

Finally, do not write code that throws types that are not derived from std::exception. This makes your code unidiomatic, and if you do want to handle the exception, you'll need to litter the code with several catch statements, like you have in your example.

查看更多
做自己的国王
3楼-- · 2019-04-07 19:16

It is possible using std::exception_ptr:

Live demo link.

#include <iostream>
#include <exception>
#include <stdexcept>

void universal_exception_handler(std::exception_ptr e)
{
    try
    {
        std::rethrow_exception(e);
    }
    catch (const std::logic_error& e)
    {
        std::cout << "logic_error" << std::endl;
    }
    catch (const std::runtime_error& e)
    {
        std::cout << "runtime_error" << std::endl;
    }
}

void foo()
{
    throw std::logic_error{""};
}

void bar()
{
    throw std::runtime_error{""};
}

int main()
{
    try
    {
        foo();
    }
    catch (...)
    {
        universal_exception_handler(std::current_exception());
    }

    try
    {
        bar();
    }
    catch (...)
    {
        universal_exception_handler(std::current_exception());
    }
}

You can also achieve this without std::exception_ptr, just put throw; in place of std::rethrow_exception(e);, hoping this function will be invoked only if there is an active exception being handled (otherwise your program will be terminate()'ed):

void universal_exception_handler()
{
    try
    {
        throw;
    }
    catch (const std::logic_error& e)
    {
        std::cout << "logic_error" << std::endl;
    }
    catch (const std::runtime_error& e)
    {
        std::cout << "runtime_error" << std::endl;
    }
}

try
{
    foo();
}
catch (...)
{
    universal_exception_handler();
}

Yet another live demo link.

查看更多
登录 后发表回答