4

I have a BaseClass and some derived classes

#ifndef TEST_H__
#define TEST_H__

#include <iostream>
#include <memory>

class BaseClass
{
  public:
  virtual double eval(double x) const = 0;
};

class Square: public BaseClass
{
  public:
  double eval(double x) const {return x*x;}
};

class Add1: public BaseClass
{
  public:
  Add1(BaseClass & obj): obj_(obj) {}

  double eval(double x) const {return obj_.eval(x) + 1.0;}

  private:
  BaseClass & obj_;
};

#endif /* TEST_H__ */

which are treated with SWIG à la

%module test

%{
#define SWIG_FILE_WITH_INIT
%}

%{
#include "test.h"
%}

%include "test.h"

This can be used from Python like

import test
s = test.Square()
a = test.Add1(s)
print(a.eval(2.0))

What's segfaulting:

import test
a = test.Add1(test.Square())
print(a.eval(2.0))

Why? The test.Square() is not assigned to a variable, so doesn't exist anymore after the assignment to a, and obj_ points to invalid storage.

To avoid such behavior, the idea to to use std::shared_ptr<BaseClass> instead of BaseClass&, i.e.

class Add1: public BaseClass
{
  public:
  Add1(std::shared_ptr<BaseClass> & obj): obj_(obj) {}

  double eval(double x) const {return obj_->eval(x) + 1.0;}

  private:
  std::shared_ptr<BaseClass> obj_;
};

This exact code won't work though with

TypeError: in method 'new_Add1', argument 1 of type 'std::shared_ptr< BaseClass > &'

Makes sense, too: test.Square() doesn't return a std::shared_ptr<BaseClass> but simply a Square aka BaseClass instance.

Is it possible to have test.Square() return a shared pointer std::shared_ptr<Square>?

Nico Schlömer
  • 53,797
  • 27
  • 201
  • 249
  • 1
    It is possible to do that, but that's not what you want - you want `test.Square()` to return a shared pointer of type `std::shared_ptr` and then have `test.Add1()` implicitly convert them, because that means you get to use `Square` specific members inside Python. I'll work up a demo – Flexo Aug 20 '16 at 18:23
  • That's what I intended, too. Clarified in the text. Thanks! – Nico Schlömer Aug 20 '16 at 18:29

1 Answers1

10

SWIG has pretty good support for std::smart_ptr. It all happens pretty transparently, so the changes you need to make to your .i file are just:

%module test

%{
#define SWIG_FILE_WITH_INIT
#include "test.h"
%}

%include <std_shared_ptr.i>

%shared_ptr(Square);
%shared_ptr(BaseClass);
%shared_ptr(Add1); // Not actually needed to make your demo work, but a good idea still

%include "test.h"

That was sufficient to make your demo Python code work, I also added onlySquare() as a member function of Square and adapted the demo to illustrate it:

import test
sq=test.Square()
test.Add1(sq) # implicitly converted to shared_ptr<BaseClass> here
sq.onlySquare()
print sq
# <test.Square; proxy of <Swig Object of type 'std::shared_ptr< Square > *' at 0xf7424950> >

It should 'just work' for non-smart pointer arguments too, but note that now all Python created instances in that hierarchy will be 'smart'.

(If you're interested, I've also covered std::unique_ptr and std::weak_ptr before).

Community
  • 1
  • 1
Flexo
  • 87,323
  • 22
  • 191
  • 272