2013-06-12 2 views
0

관찰자 패턴은 Cython 바인딩을 통해 Python 인터프리터에 노출하려는 내 C++ 프로젝트에 자주 나타납니다. 상황을 보여주는 최소한의 예를 만들려고했습니다. Spectacle은 추상 기본 클래스 Observer에서 파생 된 모든 객체 (예 : Onlooker)를 허용합니다. Spectacle::event()으로 전화하면 등록 된 각 관찰자에게 통보됩니다. 관찰자 패턴을 Cython으로 둘러보기

파일 ObserverPattern.h의 내용이다 :

class Spectacle { 
private: 
    std::vector<Observer*> observers; 
public: 

    Spectacle() {}; 
    virtual ~Spectacle() {}; 

    virtual void registerObserver(Observer* observer) { 
     this->observers.push_back(observer); 
    } 

    virtual void event() { 
     std::cout << "event triggered" << std::endl; 
     for (Observer* observer : this->observers) { 
      observer->onEvent(); 
     } 
    } 
}; 

class Observer { 
public: 
    Observer() {}; 
    virtual ~Observer() {}; 
    virtual void onEvent() = 0; 

}; 

class Onlooker : public Observer { 
public: 
    Onlooker() {}; 
    virtual ~Onlooker() {}; 
    virtual void onEvent() { 
     std::cout << "event observed" << std::endl; 
    } 
}; 

그리고이 바인딩을 포함하는, 내 .pyx 파일의 내용이다 : event() 인 경우

cdef extern from "ObserverPattern.h": 
     cdef cppclass _Spectacle "Spectacle": 
      _Spectacle() except + 
      void registerObserver(_Observer* observer) 
      void event() 

    cdef extern from "ObserverPattern.h": 
     cdef cppclass _Observer "Observer": 
      _Observer() except + 
      void onEvent() 

    cdef extern from "ObserverPattern.h": 
     cdef cppclass _Onlooker "Onlooker": 
      _Onlooker() except + 
      void onEvent() 

    cdef class Spectacle: 
     cdef _Spectacle _this 

     def event(self): 
      self._this.event() 

     def registerObserver(self, Observer observer): 
      self._this.registerObserver(observer._this) 

    cdef class Observer: 
     cdef _Observer* _this # must be a pointer because _Observer has pure virtual method 

    cdef class Onlooker(Observer): 
     pass # what should be the class body? 

이 컴파일 않지만, 세그먼테이션 폴트 (segfault)를 옵서버에게 알립니다 :

>>> spec = CythonMinimal.Spectacle() 
>>> look = CythonMinimal.Onlooker() 
>>> spec.registerObserver(look) 
>>> spec.event() 
event triggered 
Segmentation fault: 11 

여기서의 문제점은 무엇이며 어떻게 수정 될 수 있습니까?

+0

스펙터클과 비슷한 방법으로 cdef 클래스 옵서버를 만든 다음 def registerObserver (self, Observer observer) : self._this.registerObserver (& observer._this)'입니다. –

+0

@CzarekTomczak 예제를 확장하고 조언을 통합했지만 여전히 문제가 있습니다. 'observer._this'는'_Observer'의 인스턴스가 될 수 없습니다. 왜냐하면'_Observer'는 abstract (순수 가상 메소드를가집니다)이기 때문입니다. 나는 그것을 컴파일하기 위해'_Observer *'로 변경했다. – clstaudt

답변

3

문제는 본질적으로 "Python에서 C++ 인터페이스 구현"입니다.

이 작업을 수행하는 유일한 휴대용 방법은 실제 C++ 클래스 을 작성하여 파이썬에 다시 호출하는 것입니다.

Cython은 Cython 구문을 사용하여 C++ 클래스를 만들 수있는 experimental_cpp_class_def 옵션이 문서화되지 않았습니다. 예쁘지는 않지만 (IMO), 많은 시나리오에서 을 사용합니다. 여기

당신이 Observer을 구현할 수있는 방법이라는 제공 파이썬 호출로 대표 :

from cpython.ref cimport PyObject, Py_INCREF, Py_DECREF 

cdef cppclass ObserverImpl(_Observer): 
    PyObject* callback 

    __init__(object callback): # constructor. "this" argument is implicit. 
     Py_INCREF(callback) 
     this.callback = <PyObject*>callback 

    __dealloc__(): # destructor 
     Py_DECREF(<object>this.callback) 

    void onEvent(): 
     (<object>this.callback)() # exceptions will be ignored 

그리고 당신이 그것을 사용할 수있는 방법은 다음과 같습니다 단지 C 구조 같은

def registerObserver(self, callback not None): # user passes any Python callable 
    self._this.registerObserver(new ObserverImpl(callback)) 

C++ 객체를, Cython 관리 object 참조를 보유 할 수 없습니다. 따라서 PyObject* 필드를 사용하고 참조 번호 을 직접 관리해야합니다. 물론 내부 메소드를 사용하면 Cython 기능으로 캐스트하고 사용할 수 있습니다.

또 다른 까다로운 순간은 예외 전파입니다. onEvent() 메서드는 C++에서 정의되었으므로 Python 예외를 전파 할 수 없습니다. Cython은 전달할 수없는 예외를 무시합니다. 더 잘하고 싶다면, 직접 잡아서 나중에 검사하거나 C++ 예외로 다시 올리십시오. (Cython 구문에서 C++ 예외를 throw하는 것은 불가능하지만 외부 throwing 도우미 함수를 호출 할 수는 있습니다.)

관찰자가 둘 이상의 메소드를 사용하는 경우 callback은 직접 호출하는 대신 Python 클래스가됩니다. (<object>this.callback).onEvent()과 같은 메서드를 호출 할 수 있습니다.

분명히 ObserverImpl도 C++로 직접 코딩 할 수 있습니다. Py_INCREF, Py_DECREFPyObject_Call/PyObject_CallMethod은 필요한 유일한 Python API입니다.

+0

해명 해 주셔서 감사합니다. 내가 두려워했던 것처럼, 이것은 전혀 사소한 것이 아니지만 이것을 시도 할 것입니다. – clstaudt

+0

@Nikita Nemkin : 예외를 전파하는 올바른 구문을 제외하고'onEvent() void *입니까? 아니면 어떻게해야합니까? –

+0

'except' 절은 _Cython_에 신호를 보내고 예외를 확인하는 방법 만 알려줍니다. 'onEvent'는 _C++ _에서 호출되며 C++은 파이썬 예외 시스템에 대해 아무것도 모릅니다. 타사 스택 프레임에서 파이썬 예외를 전파하는 간단한 솔루션은 없습니다. 해결 방법에 대한 개요를 이미 설명했습니다. 1) 콜백 예외를 무시합니다. 2) 저장하고 나중에 확인합니다. 3) C++ 예외와 같은 타사 오류보고 시스템에서 피기 백 (piggyback)합니다. –

관련 문제