2014-12-29 40 views
5

Mam klasę EventDispatcher, która implementuje wzorzec subskrypcji publikowania. Interfejs wygląda mniej więcej tak: (uproszczony):Jak radzić sobie z unique_ptr z SWIG

class EventDispatcher 
{ 
public: 
    void publish(const std::string& event_name, std::unique_ptr<Event> event); 

    std::unique_ptr<Subscription> subscribe(const std::string& event_name, std::unique_ptr<Callback> callback); 

private: 
    std::unordered_map<std::string, std::vector<std::unique_ptr<Callback>>> m_subscriptions; 
} 

Chcę udostępnić tę klasę w języku Python. Najnowszą dokumentację SWIG stwierdza:

Nie ma specjalnego inteligentny wskaźnik obsługi dostępny dla std :: weak_ptr i std :: unique_ptr jeszcze.

Chciałbym przynajmniej móc nadal używać unique_ptr na stronie C++. Jakie są moje opcje?

Rozważałem rozszerzenie klasy przy użyciu funkcji% rozszerzenia SWIG, ale nie mogę uzyskać dostępu do prywatnych członków (m_subscriptions) przy użyciu tej metody.

Jedyna opcja widzę jest użycie preprocesora haust zdefiniowanie dodatkowych metod, swig_publish i swig_subscribe, ale to zaśmiecanie mój plik interfejs.

Odpowiedz

10

Jest sporo zakresu robić pożyteczne rzeczy używając generic smart pointer support w SWIG, mimo braku wsparcia odnotowano w C++ 11 notatek.

W skrócie, jeśli istnieje operator->, wówczas SWIG połączył członków osoby zainteresowanej do wskaźnika, aby umożliwić im używanie zamiennie w języku docelowym przez długi czas.

I już ułożyła kompletny przykład jak to może działać dla Ciebie, używając test.hh poniższym przykładzie pliku hader:

#include <memory> 
#include <iostream> 

struct Foobar { 
    void baz() { std::cout << "This works\n"; } 
    int wibble; 
}; 

std::unique_ptr<Foobar> make_example() { 
    return std::unique_ptr<Foobar>(new Foobar); 
} 

void dump_example(const std::unique_ptr<Foobar>& in) { 
    std::cout << in->wibble << "\n"; 
    in->baz(); 
} 

W celu skorzystania z unique_ptr rozsądnie wewnątrz Python miałem napisać następujący plik SWIG, std_unique_ptr.i:

namespace std { 
    %feature("novaluewrapper") unique_ptr; 
    template <typename Type> 
    struct unique_ptr { 
    typedef Type* pointer; 

    explicit unique_ptr(pointer Ptr); 
    unique_ptr (unique_ptr&& Right); 
    template<class Type2, Class Del2> unique_ptr(unique_ptr<Type2, Del2>&& Right); 
    unique_ptr(const unique_ptr& Right) = delete; 


    pointer operator->() const; 
    pointer release(); 
    void reset (pointer __p=pointer()); 
    void swap (unique_ptr &__u); 
    pointer get() const; 
    operator bool() const; 

    ~unique_ptr(); 
    }; 
} 

%define wrap_unique_ptr(Name, Type) 
    %template(Name) std::unique_ptr<Type>; 
    %newobject std::unique_ptr<Type>::release; 

    %typemap(out) std::unique_ptr<Type> %{ 
    $result = SWIG_NewPointerObj(new $1_ltype(std::move($1)), $&1_descriptor, SWIG_POINTER_OWN); 
    %} 

%enddef 

który zawiera dość podzbioru definicji std::unique_ptr być użyteczne. (Możesz dodawać lub usuwać konstruktory w zależności od tego, jaką semantykę chcesz zastosować w Pythonie, przeoczyłem tutaj niestandardowe narzędzia do usuwania).

Dodaje także makro wrap_unique_ptr, które ustawia obsługę. Typografia wymusza na wygenerowanym przez SWIG kodzie użycie konstruktora ruchu zamiast konstruktora kopiowania po powrocie przez wartość.

Możemy używać go w następujący sposób:

%module test 

%{ 
#include "test.hh" 
%} 

%include "std_unique_ptr.i" 

wrap_unique_ptr(FooUniquePtr, Foobar); 

%include "test.hh" 

Zbudowałem to z:

swig3.0 -py3 -c++ -python -Wall test.i 
g++ -Wall -Wextra -Wno-missing-field-initializers test_wrap.cxx -std=c++11 -I/usr/include/python3.4/ -lpython3.4m -shared -o _test.so 

co pozwala nam na stosowanie następującej Python:

from test import * 

a = make_example() 

print(a) 
a.wibble = 1234567 
a.baz() 

dump_example(a) 

a.baz() 

print(bool(a)) 
print(bool(FooUniquePtr(None))) 

b=a.release() 
print(b) 

Zauważ, że pomimo tego, że jest to unique_ptr<Foobar>, nadal możemy powiedzieć: a.baz() i a.wibble. Metoda release() zwraca również użyteczny wskaźnik "raw", który jest obecnie własnością Python (ponieważ w przeciwnym razie nie miałby właściciela). get() zwraca pożyczony wskaźnik wewnątrz Pythona, jak można się spodziewać.

W zależności od tego, jak planujesz używać wskaźników, jest to prawdopodobnie dobry początek dla twoich własnych map i czystszych niż %extend i release() wszędzie gdzie masz unique_ptrs.

W porównaniu z %shared_ptr nie modyfikuje to w mapach typów i nie zmienia konstruktorów w taki sam sposób, w jaki działałaby współużytkowana_ptr. Twoim obowiązkiem jest wybrać, kiedy surowe wskaźniki stają się wciąż unikalnymi_plikami w Pythonie.

Napisałem podobną odpowiedź dla using std::weak_ptr with SWIG chwilę temu.

+0

Jedną z rzeczy, które można łatwo dodać do ulepszenia, jest typografia dla 'Type *'/'Type &', który może obsłużyć unikalny_ptr lub prawdziwy wskaźnik. – Flexo

+0

Interesujące. Zgadzam się, że jest to o wiele czystsze niż zabrudzanie plików interfejsu z dodatkowymi funkcjami do obsługi konwersji pomiędzy unikalnymi wskaźnikami a surowymi wskaźnikami. Pokazuje również wyraźną intencję właściciela. Dziękuję za szczegółową odpowiedź. – Homar

+0

Uwaga: Moja klasa miała prywatny 'std :: unique_ptr pImpl', w którym to przypadku musiałem ** nie ** zawierać żadnych' wrap_unique_ptr (RealImplUniquePtr, RealImpl) '(które dawałyby błędy o' niekompletnym typie 'od' default_delete'), po prostu umieść typy, które były w pełni dostępne z publicznego interfejsu API. – unhammer

2

dziwnie, wydaje się, że możliwe jest %ignore funkcją, %extend klasa zdefiniować alternatywną realizację funkcji ignorowane i wreszcie wywołać początkowo zignorowano funkcję z alternatywnej implementacji tej funkcji. Na przykład:

%ignore EventDispatcher::subscribe(const std::string&, std::unique_ptr<Callback>); 

%include "EventDispatcher.hpp" 

%extend suborbital::EventDispatcher 
{ 
    EventSubscription* EventDispatcher::subscribe(const std::string& event_name, PyObject* callback) 
    { 
     std::unique_ptr<Callback> callback_ptr(new Callback(callback)); 
     return $self->subscribe(event_name, std::move(callback_ptr)).release(); 
    } 
} 
+0

Możesz to zrobić bezpośrednio, bez wywoływania polecenia% ignore, dopóki nie wystawisz (nie zadeklarujesz) oryginalnego prototypu funkcji w pliku .i – n00shie