How to handle unique_ptr's with SWIG

Question:

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.

Asked By: Homar

||

Answers:

There is no support to unique_ptr yet.
http://www.swig.org/Doc3.0/CPlusPlus11.html

You need to use smart pointers as follow:
http://www.swig.org/Doc3.0/Library.html#Library_std_shared_ptr

Answered By: Jose

Bizarrely, it seems that it is possible to %ignore a function, %extend the class to define an alternative implementation of the ignored function and finally call the initially ignored function from within the alternative implementation of that function. For example:

%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();
    }
}
Answered By: Homar

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 worksn"; }
  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.

Answered By: Flexo

SWIG-4.1 has had std::unique_ptr support added. Documentation at https://swig.org/Doc4.1/Library.html#Library_std_unique_ptr.

Answered By: William S Fulton
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.