10

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.

Homar
  • 1,520
  • 2
  • 17
  • 26

4 Answers4

19

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.

Community
  • 1
  • 1
Flexo
  • 87,323
  • 22
  • 191
  • 272
  • 1
    One of the things that would be easy to add to enhance this would be an in typemap for `Type*`/`Type&` that can handle either a unique_ptr or a real pointer. – Flexo Dec 30 '14 at 11:44
  • Interesting. I agree that this is much cleaner than muddying the interface files with additional functions to handle conversions between unique_ptr's and raw pointers. It also shows clear ownership intention. Thank you for the detailed answer. – Homar Dec 30 '14 at 14:25
  • Note: My class had a private `std::unique_ptr pImpl`, in which case I had to **not** include any `wrap_unique_ptr(RealImplUniquePtr, RealImpl)` (that would give errors about `incomplete type` from `default_delete`), just wrap the types that were fully available from the public API. – unhammer Dec 12 '17 at 13:34
3

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();
    }
}
Homar
  • 1,520
  • 2
  • 17
  • 26
  • You can do this directly without calling %ignore, as long you don't expose (declare) in the original function prototype in the .i file – n00shie Sep 27 '16 at 16:27
  • I did this, but also returned PyNone if the unique_ptr was `nullptr`. – dbn May 05 '22 at 16:41
1

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

-2

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

Jose
  • 99
  • 1
  • 4
  • I don't really want to use a different type of smart pointer. I'm interested in how I can continue using unique_ptr's on the c++ side but expose different functions to Python that use raw pointers. – Homar Dec 29 '14 at 18:51