2015-01-07 4 views
2

이 질문은 here에서 따릅니다. 그러나, 이전의 질문은 나쁘게 (사실은 잘못) 말한 것이므로 처음부터 다시 묻는 것이 좋습니다.예외 안전 트램폴린을위한 디자인 패턴

나는 C- 함수 포인터 테이블을 가지고있다.

일부 C 코드 (lib-X라고 부름)에는 기본 구성 블록 (X-object라고 부름)이 있습니다. 각 X 객체는이 테이블에서 함수를 호출 할 수 있습니다.

이 테이블 함수는 일반적으로 여러 함수가 동일한 서명을 공유 할 수 있지만 다른 서명 (typedef here 참조)을 갖습니다. 이 테이블에는 약 100 가지 기능이 있습니다.

C++에서는 각 X- 개체에 대해 Final : Base 클래스가 연결되어 있습니다.

그리고이 호출을 X 객체의 해당 C++ Final 인스턴스에 전달하려고하지만 C++ 소비자가 버그가있는 Final을 제공 할 수 있으므로 try/catch 내에이 객체를 묶고 싶습니다.

그래서 테이블의 각 항목에 가상 함수가있는 C++ Base 클래스가 있습니다.

그런 다음 기본 클래스에서 파생 된 C++ 최종 클래스 (가능한 많은, 최종 1 결승 2 결승 3 등)가 있습니다.

그래서 지금은 단지

  • (항상 함수를 호출 한 X-개체에 대한 포인터 될 것입니다)

    1. 는 첫 번째 '자기'매개 변수를 가져옵니다하는 핸들러를 작성해야 연관된 C++ 기본 클래스 인스턴스를 검색합니다. 시도의 catch 블록 내에서

    2. 실제로 최종의 재정의를 호출 할 ...

    3. 를 통해 남아있는 모든 매개 변수를 전달, 해당 가상 함수를 호출합니다.

    처음 시작에 대한 플롯을 이해하려고하는 것처럼 보입니다. 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)

    저는이 예외없는 안전한 트램 폴리 라인을 허용하는 컴팩트 한 디자인을 제시하려고합니다.

    내 진행 상황은 [제거됨, 아래 답변 참조]

  • +1

    (); '바닥 ("EDIT")에 연결된 코드 샘플입니다. – dyp

    +0

    @dyp, ARGH! 감사! (지금 버려진 코드는 제거되었습니다) –

    +0

    틀릴 수도 있지만 NVI (비 가상 인터페이스) 관용구가 이것을 해결하지 않았습니까? 기본 클래스에는 보호 된 도우미에게 전달하는 공개 비회원 메서드가 있습니다. 가상이 아닌 메소드는 try/catch 코드를 매우 편리하게 볼 수 있습니다. – Mark

    답변

    3

    이와 비슷한 내용입니까?

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

    어떻게 호출할까요? 'void enable_f1() {table.fp_1 = & trap ; }'? –

    +0

    함수입니다. 멤버 함수에 대한 포인터, 기본 포인터 및 기타 매개 변수를 사용하여 호출합니다. –

    3

    나는 내가 핵심 기계 (그래서 그의 대답을 upvote에하시기 바랍니다) 상승했다있는 Piotr's answer to my previous question, 덕분에 작업을 얻었다.

    Coliru 당신은`trampoline.enable_f2 호출되지 않은 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!) 
    } 
    
    +0

    이러한 멤버 함수를 호출 할 때 함수 포인터를 생성하는 대신 정적으로 채워진 테이블을 생성하고 필요할 때 항목을 복사 할 수 있습니다. 'constexpr static table base_table = {TRAP (Base :: func1), TRAP (Base :: func2), TRAP (Base :: func3)}과 같은 것입니다. – dyp