【问题标题】:Design pattern for exception-safe trampolines异常安全蹦床的设计模式
【发布时间】:2015-01-08 08:16:42
【问题描述】:

这个问题来自here。但是,上一个问题的措辞非常糟糕(实际上是错误的),建议我从头开始再问一次。

我有一个 C 函数指针表。

一些 C 代码(我们称之为 lib-X)有一个基本的构建块(我们称之为 X 对象)。每个 X 对象都可以调用此表上的函数。

这些表函数通常具有不同的签名(请参阅 typedefs here),尽管多个函数可以共享相同的签名。表中大约有 100 个这样的函数。

在 C++ 中,每个 X 对象都有一个关联的 Final:Base 类。

我想将这些调用转发到 X 对象的相应 C++ Final 实例,但我想将其包含在 try/catch 中,因为 C++ 使用者可能会提供一个有缺陷的 Final。

所以我有一个 C++ 基类,它对表中的每个条目都有一个虚函数。

然后我有一个派生自基类的 C++ Final 类(可能很多;Final1 Final2 Final3 等)。

所以现在我只需要编写一个处理程序

  1. 获取第一个“self”参数(始终是指向调用函数的 X 对象的指针)

  2. 检索关联的 C++ 基类实例。

  3. 在try catch块中,调用对应的虚函数,将剩余的参数全部转发过去,

  4. ...实际上会调用 Final 中的覆盖。

这有点像试图理解《盗梦空间》的情节。 lib-X 实际上是 Python 运行时,尽管我试图保持一般性。

问题是这样的函数有几十个,这会导致一些非常混乱且无法维护的 C++ 代码——如果我必须为每个函数手动编写一个蹦床函数,看起来像:

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
    }
}

(来源here

我正在尝试提出一种紧凑的设计,以允许这种异常安全的蹦床。

我目前的进度是[已删除,请参阅下面的答案]

【问题讨论】:

  • 您没有在底部链接的代码示例中调用trampoline.enable_f2();(“EDIT”)。
  • @dyp,啊!谢谢! (错误代码现已删除)
  • 可能是错的,但是NVI(非虚拟接口)习语不能解决这个问题吗?基类具有转发给受保护助手的公共非虚拟方法。然后,非虚拟方法可以非常方便地查看您的 try/catch 代码。

标签: c++ c++11 trampolines


【解决方案1】:

这样的?

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;
    }
}

【讨论】:

  • 我将如何调用它? void enable_f1() { table.fp_1 = &amp;trap&lt;int, &amp;Base::func_1&gt;; }?
  • 这是一个函数 - 使用指向成员函数的指针、指向基址的指针和任何其他参数调用它。
【解决方案2】:

感谢Piotr's answer to my previous question,我得到了它的工作,我从中提取了核心机制(所以请为他的回答投票)。

科利鲁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!)
}

【讨论】:

  • 除了在这些成员函数的调用中生成函数指针之外,您还可以生成一个静态填充的表并按需复制条目。类似constexpr static Table base_table = { TRAP(Base::func1), TRAP(Base::func2), TRAP(Base::func3) };
猜你喜欢
  • 1970-01-01
  • 2021-09-14
  • 2011-12-27
  • 1970-01-01
  • 2014-12-30
  • 2010-09-16
  • 2018-11-02
  • 2016-03-24
  • 2014-10-03
相关资源
最近更新 更多