Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to handle unique_ptr's with SWIG

Tags:

c++

python

swig

I have an EventDispatcher class that implements the publish-subscribe pattern. It's interface looks something like this (simplified):

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

I want to expose this class to Python. The latest SWIG documentation states that:

There is no special smart pointer handling available for std::weak_ptr and std::unique_ptr yet.

I would quite like to at least be able to continue using unique_ptr's on the c++ side. What are my options?

I considered extending the class using SWIG's %extend feature, but I am unable to access private members (m_subscriptions) using this method.

The only other option I can see is to use the SWIG preprocessor to define extra methods, swig_publish and swig_subscribe, but this clutters my interface file.

like image 963
Homar Avatar asked Dec 29 '14 18:12

Homar


1 Answers

There's quite a lot of scope to do useful things using the generic smart pointer support in SWIG, despite the noted lack of support in the C++11 notes.

In short if there's an operator-> then SWIG has merged the members of the pointee into the pointer to allow them to be used interchangeably within the the target language for a long time.

I've put together a complete example of how this might work for you, using the example hader file test.hh below:

#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();
}

In order to use the unique_ptr sensibly inside Python I had to write the following SWIG file, 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

Which includes enough of a subset of the definition of std::unique_ptr to be useful. (You can add or remove constructors depending on exactly what semantics you want within Python, I overlooked the custom deleters here).

It also adds a macro wrap_unique_ptr that sets up the support. The typemap just forces SWIG's generated code to use the move constructor instead of the copy constructor when returning by value.

We can use it in the following way:

%module test

%{
#include "test.hh"
%}

%include "std_unique_ptr.i"

wrap_unique_ptr(FooUniquePtr, Foobar);

%include "test.hh"

I built this with:

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 

Which allows us to use the following 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)

Notice that despite being a unique_ptr<Foobar> we can still say a.baz() and a.wibble. The release() method also returns a usable 'raw' pointer, which is owned by Python now (since otherwise it wouldn't have an owner). get() returns a borrowed pointer inside Python as you'd expect.

Depending on quite how you plan to use the pointers this is probably a good start for your own typemaps and cleaner than a %extend and release() everywhere you have unique_ptrs.

Compared to %shared_ptr, this doesn't modify the in typemaps and it doesn't change the constructors in the same way the shared_ptr support would. It's your responsibility to choose when raw pointers become unique_ptrs within Python still.

I wrote a similar answer for using std::weak_ptr with SWIG a while back.

like image 55
Flexo Avatar answered Oct 06 '22 00:10

Flexo