Search code examples
pythonc++abstract-classswigstdmap

SWIG c++ / python: how to handle a std::map of shared_ptr of abstract class


How can I handle the map of abstract methods in python with SWIG from the following c++ code:

class A : Base {
    virtual int f() = 0;
};

class B : public A {
    int f() { return 10 }; 
}; 

class C : public A {
    int f() { return 20 }; 
}; 

std::map< std::string, std::shared_ptr<A>> my_map; 

In python, I would like to do something similar too:

my_B = B()
my_map["foo"] = my_B

or may be simpler:

my_map["foo"] = B()

I have to precise that A or B could be director class in order to use the cross language polymorphism.

My questions:

  1. What could be the minimal .i file associated to this problem ?
  2. I also read that this can cause python / C++ tricky ownership problem if for instance my_B is deleted. How can I easily tranfer "my_B" ownership from python to C++ ?

Thanks a lot for your help

A.


Solution

  • Here's a working example with tracking for construction/destruction to show reference counting of the shared pointer is working:

    test.h

    #include <map>
    #include <memory>
    #include <string>
    #include <iostream>
    
    class A {
    public:
        virtual int f() = 0;
        A() { std::cout << "A()" << std::endl; }
        virtual ~A() { std::cout << "~A()" << std::endl; }
    };
    
    class B : public A {
    public:
        int f() { return 10; }
        B() { std::cout << "B()" << std::endl; }
        virtual ~B() { std::cout << "~B()" << std::endl; }
    };
    
    class C : public A {
    public:
        int f() { return 20; }
        C() { std::cout << "C()" << std::endl; }
        virtual ~C() { std::cout << "~C()" << std::endl; }
    };
    
    std::map< std::string, std::shared_ptr<A>> my_map;
    

    test.i

    %module test
    
    %{
    #include "test.h"
    %}
    
    %include <std_map.i>
    %include <std_shared_ptr.i>
    %include <std_string.i>
    
    // declare all visible shared pointers so SWIG generates appropriate wrappers
    // before including the header.
    %shared_ptr(A)
    %shared_ptr(B)
    %shared_ptr(C)
    
    %include "test.h"
    
    // Declare the template instance used so SWIG will generate the wrapper.
    %template(Map) std::map<std::string, std::shared_ptr<A>>;
    

    Output:

    >>> import test
    >>>
    >>> m=test.cvar.my_map    # global variables are in module's cvar.
    >>> m['foo'] = test.C()
    A()
    C()
    >>> m['foo'].f()
    20
    >>> del m['foo']  # only reference, so it is freed
    ~C()
    ~A()
    >>> b = test.B()  # 1st reference
    A()
    B()
    >>> m['bar'] = b  # 2nd reference
    >>> del m['bar']  # NOT freed.
    >>> del b         # now it is freed.
    ~B()
    ~A()