Design pattern for exception-safe trampolines

核能气质少年 提交于 2019-12-01 08:23:48

问题


This question follows from here. However, the previous question was worded so badly (wrongly in fact) that it was suggested I ask again from scratch.

I have a table of C-function pointers.

Some C code (let us call it lib-X) has a basic building block (let's call it a X-object). Each X-object can invoke functions on this table.

These table functions generally have different signatures (see typedefs here), although it is possible for several functions to share the same signature. There are about 100 of these functions in the table.

In C++ I have an associated Final:Base class for each X-object.

And I want to forward these calls to the X-object's corresponding C++ Final instance, but I want to enclose this within try/catch, as the C++ consumer may provide a buggy Final.

So I have a C++ Base class that has a virtual function for each entry on the table.

I then have a C++ Final class (possibly many; Final1 Final2 Final3 etc) that derives from the base class.

So now I just need to write a handler that

  1. Gets the first 'self' parameter (which will always be a pointer to the X-object that invoked the function)

  2. Retrieves the associated C++ base class instance.

  3. Within a try catch block, invokes the corresponding virtual function, forwarding all the remaining parameters through,

  4. ... which will actually invoke the override in Final.

It's a bit like trying to understand the plot for Inception. lib-X is in fact the Python runtime, although I'm trying to keep things general.

The thing is there are dozens of these functions, and this makes for some very messy and unmaintainable C++ code -- if I have to manually write a trampoline function for each one, looking like:

extern "C" PyObject *call_handler( PyObject *self, PyObject *args, PyObject *kw )
{
    try
    {
        PythonExtensionBase *p = getPythonExtensionBase( self );
        if( kw != NULL )
            return new_reference_to( p->call( Object(args), :Object(kw) ) );
        else
            return new_reference_to( p->call( Object(args), Object() ) );
    }
    catch( Py::Exception & )
    {
        return NULL; // indicate error
    }
}

(source here)

I'm trying to come up with a compact design that allows this exception-safe trampolining.

My current progress is [REMOVED, see answer below]


回答1:


something like this?

template<typename RET, class ...Args> // <-- one trap for each f in Base that gets enabled!
RET trap( RET (Base::*f)(Args...), void* self, Args&&...args )
{
    try {
        auto base = reinterpret_cast<Base*>(self);
        return (base->*f)(std::forward<Args>(args)...);
    }
    catch (...) {
        return (RET)0;
    }
}



回答2:


I got it working thanks to Piotr's answer to my previous question, from which I have lifted the core machinery (so please upvote his answer).

Coliru here

#include <iostream>
#include <typeinfo>

class Base {
public:
    virtual int   func_1( int a )        { std::cout << "Base::func_1" << std::endl; return a; }
    virtual float func_2( int a, int b ) { std::cout << "Base::func_2" << std::endl; return a+b; }
    virtual float func_3( char a )       { std::cout << "Base::func_3" << std::endl; return (float)a; }
};

class Final : public Base {
public:
    int   func_1( int a )           override { std::cout << "Final::func_1" << std::endl; return a+1000; }
  //float func_2( int a, int b )    override { std::cout << "Final::func_2" << std::endl; return a*b; }
    float func_3( char a )          override { std::cout << "Final::func_3" << std::endl; throw 666; }
};

Base* get_base(void* s) {
    return reinterpret_cast<Base*>(s);
}

template <typename T, T t>
struct trap;

template <typename R, typename... Args, R(Base::*t)(Args...)>
struct trap<R(Base::*)(Args...), t>
{    
    static R 
    call(void* s, Args... args)
    {
        std::cout << "trap:" << typeid(t).name() << std::endl;
        try
        {
            return (get_base(s)->*t)(std::forward<Args>(args)...);
        }
        catch (...)
        {
            std::cout << "CAUGHT" << std::endl;
            return std::is_integral<R>::value ? static_cast<R>(-42) : static_cast<R>(-3.14); 
        }
    }
};


#define TRAP(f)  & trap<decltype(&f), &f>::call

class Trampoline 
{
    using F1 = auto ( void* self, int a )         -> int;
    using F2 = auto ( void* self, int a, int b )  -> float;
    using F3 = auto ( void* self, char a )        -> float;

    struct Table {
        F1* fp_1;
        F2* fp_2;
        F3* fp_3;
    };
public:
    Table* table = new Table();

    void enable_f1() { table->fp_1 = TRAP( Base::func_1 ); }
    void enable_f2() { table->fp_2 = TRAP( Base::func_2 ); }
    void enable_f3() { table->fp_3 = TRAP( Base::func_3 ); }
};

int main()
{
    Trampoline trampoline{};

    trampoline.enable_f1();
    trampoline.enable_f2(); 
    trampoline.enable_f3(); 

    Final final{};

    void* base_as_pvoid = (void*)static_cast<Base*>(&final);

    // test
    int u    = trampoline.table->fp_1( base_as_pvoid, 2 );     std::cout << u << std::endl; // expect: 1002   (enabled and Final provides override)
    float v  = trampoline.table->fp_2( base_as_pvoid, 3, 5 );  std::cout << v << std::endl; // expect: 8      (enabled but no override)
    float w  = trampoline.table->fp_3( base_as_pvoid, 'x' );   std::cout << w << std::endl; // expect: -3.14  (enabled and Final provides override, which throws!)
}


来源:https://stackoverflow.com/questions/27826117/design-pattern-for-exception-safe-trampolines

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!