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
Gets the first 'self' parameter (which will always be a pointer to the X-object that invoked the function)
Retrieves the associated C++ base class instance.
Within a try catch block, invokes the corresponding virtual function, forwarding all the remaining parameters through,
... 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]
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;
}
}
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