Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Wrapping a set of C++ classes


Overview

This page demonstrates how to wrap a more complicated pair of C++ classes than the basic example. We have two classes, one of which inherits from the other, and both constructors can take a pointer to an instance of the base class to act as a parent to the new instance.

As with any C++ wrapping, Cython 0.13 (when C++ support was added) or later is required.

The C++ code

The basic C++ API we want to wrap is given below. We will assume it is kept in the header file classes.h.

class BaseClass{
    public:
            BaseClass(BaseClass *parent);
            ~BaseClass();
};

class MainClass: public BaseClass{
    public:
            MainClass(BaseClass *parent);
            ~MainClass();
            void accumulate(int new_number);
            int get_total();
    private:
            int total;
};

Note that the constructors all take a pointer to an instance of the base class. This means an instance of any sub-class can be used as the parent of another, so we can chain different sub-classes together in an arbitrary fashion. If an instance is an orphan (i.e., it has no parent), the parent argument to the constructor will be a null pointer.

For the purposes of the example, we will use the following implementation which prints out various bits of information. This will be kept in classes.cpp.

#include "classes.h"
#include <iostream>

MainClass::MainClass(BaseClass *parent): BaseClass(parent), total(0)
{
    std::cout << "   C++: creating MainClass at " << this;
    if(parent) std::cout << " with parent at " << parent;
    std::cout << std::endl;
}

MainClass::~MainClass()
{
    std::cout << "   C++: destroying MainClass at " << this << std::endl;
}

void MainClass::accumulate(int new_number){
    this->total += new_number;
}

int MainClass::get_total(){
    return this->total;
}

BaseClass::BaseClass(BaseClass *parent)
{
  std::cout << "   C++: creating BaseClass at " << this;
  if(parent) std::cout << " with parent at " << parent;
  std::cout << std::endl;
}

BaseClass::~BaseClass()
{
  std::cout << "   C++: destroying BaseClass at " << this << std::endl;
}

Cython definition

The first step is to create a Cython definitions file, classes.pxd, to tell Cython what the C++ API looks like:

cdef extern from "classes.h":
    cdef cppclass BaseClass:
        BaseClass(BaseClass *parent)

    cdef cppclass MainClass:
        MainClass(BaseClass *parent)
        void accumulate(int new_number)
        int get_total()

Wrapping the base class

Next, we start our wrapper file - imaginatively named wrapper.pyx - by importing the definition file. We then define an extension class which will wrap the base class; this requires it to have a cdef pointer to an instance of the C++ BaseClass.

The __cinit___ method will take one parameter, parent, which must either be an instance of the extension class (or a future sub-class), or be None if there is no parent. By limiting the parameter to these choices, we can extract the pointer to the underlying C++ instance from the Python object, and use that to create the C++ instance for this class.

The __dealloc__ method is simpler - it simply checks that a C++ instance was successfully constructed and, if so, destroys it to avoid a memory leak.

With the appropriate error checking (and continuing to print status messages), the wrapper for the base class is as follows:

cimport classes

cdef class BaseClass:
    cdef classes.BaseClass *wrapped

    def __cinit__(self, BaseClass parent=None):
        print "Cython: running BaseClass.__cinit__ with parent =", parent

        # Create a C++ pointer to the base class, and initialise it to null.
        cdef classes.BaseClass *base = NULL

        # If we have a parent, retrieve the pointer to the wrapped base class.
        if parent is not None:
            base = parent.wrapped

        # Try to create an instance of the wrapped class.
        self.wrapped = new classes.BaseClass(base)

        # If we couldn't create one, we're out of memory.
        if self.wrapped is NULL:
            raise MemoryError()

    def __dealloc__(self):
        print "Cython: running BaseClass.__dealloc__ on", self
        if self.wrapped is not NULL:
            del self.wrapped

Having compiled this, we can test it in a Python session:

>>> import wrapper
>>> bc1 = wrapper.BaseClass()
Cython: running BaseClass.__cinit__ with parent = None
   C++: creating BaseClass at 0x922d540
>>> bc2 = wrapper.BaseClass(bc1)
Cython: running BaseClass.__cinit__ with parent = <wrapper.BaseClass object at 0xb770a090>
   C++: creating BaseClass at 0x921b638 with parent at 0x922d540
>>> quit()
Cython: running BaseClass.__dealloc__ on <wrapper.BaseClass object at 0xb770a090>
   C++: destroying BaseClass at 0x922d540
Cython: running BaseClass.__dealloc__ on <wrapper.BaseClass object at 0xb770a080>
   C++: destroying BaseClass at 0x921b638

Wrapping the main class

Attempt 1

Next, we want to wrap the main class. We start off by telling Cython it is a sub-class of BaseClass; this allows it to be used as a parent object. This also copies the definition of the wrapped attribute across to this class. As this is defined as a pointer to a C++ BaseClass instance, we have to cast the C++ MainClass instance to a BaseClass instance when storing it, and back when we want to use it.

Our first attempt at the wrapper ends up as follows:

cdef class MainClass(BaseClass):
    def __cinit__(self, BaseClass parent=None):
        print "Cython: running MainClass.__cinit__ with parent =", parent

        # Create a C pointer to the base class, and initialise it to null.
        cdef classes.BaseClass *base = NULL

        # If we have a parent, retrieve the pointer to the wrapped base class.
        if parent is not None:
            base = parent.wrapped

        # Create an instance of the wrapped class. We have to cast it to
        # BaseClass to store it in the wrapped attribute.
        self.wrapped = <classes.BaseClass *>new classes.MainClass(base)

        # If we couldn't create one, we're out of memory.
        if self.wrapped is NULL:
            raise MemoryError()

    def __dealloc__(self):
        print "Cython: running MainClass.__dealloc__ on", self

        # If we successfully created an instance, cast it to a MainClass and
        # destroy it.
        cdef classes.MainClass *temp
        if self.wrapped is not NULL:
            temp = <classes.MainClass *>self.wrapped
            del temp

    def accumulate(self, new_number):
        (<classes.MainClass *>self.wrapped).accumulate(new_number)

    property total:
        def __get__(self):
            return (<classes.MainClass *>self.wrapped).get_total()

However, if we compile it and test in a Python terminal, we find a couple of problems:

>>> import wrapper
>>> mc = wrapper.MainClass()
Cython: running BaseClass.__cinit__ with parent = None
   C++: creating BaseClass at 0x8fad648
Cython: running MainClass.__cinit__ with parent = None
   C++: creating BaseClass at 0x8fc6408
   C++: creating MainClass at 0x8fc6408
>>> quit()
Cython: running MainClass.__dealloc__ on <wrapper.MainClass object at 0xb78790d0>
   C++: destroying MainClass at 0x8fc6408
   C++: destroying BaseClass at 0x8fc6408
Cython: running BaseClass.__dealloc__ on <wrapper.MainClass object at 0xb78790d0>
   C++: destroying BaseClass at 0x8fc6408
*** glibc detected *** python: double free or corruption (fasttop): 0x08fc6408 ***

First, we've created two C++ objects, one at 0x8fad648 and the other at 0x8fc6408; the first of these is now a memory leak as the pointer to it will have been overwritten by the second. Second, when cleaning up at the end we've tried to free the object twice.

Both of these are due to the way Cython handles sub-classes. When creating an instance of one, it goes through the inheritance list top-down, i.e., it starts at the top-level class (BaseClass for us) and runs its __cinit___ method, and works it way down the inheritance list running every __cinit__ method until it reaches the bottom. When destroying an instance, it goes through the list in the opposite direction.

With our code, this means BaseClass.__cinit__ is called first and creates the corresponding C++ !BaseClass instance. When this is done, MainClass.__cinit__ is called, and creates a C++ !MainClass instance. As this is stored in the same attribute, the first pointer is overwritten and creates a memory leak. When destroying the object, the MainClass.__dealloc__ method is called first and calls the corresponding C++ destructor, thus freeing the memory the instance takes. The BaseClass.__dealloc__ method is then called, and tries to do the same.

Attempt 2

Fixing this is actually quite straightforward. In both cases we want the code in MainClass to be run, and the BaseClass code to be ignored. For the destructor, this is easy as Cython calls MainClass.__dealloc__ first. All we need to do is set self.wrapped to NULL, and then BaseClass.__dealloc__ won't try to free anything.

The constructor is called in the opposite order to what we want, so we have to find some other way of preventing it running. The simplest way is to edit the BaseClass.__cinit__ method and add some type-checking to ensure that self - the object actually being created - is actually a BaseClass before running the code (of course, we should also add this type checking to the MainClass constructor to allow for any future sub-classes we might wrap).

The overall wrapper code now looks as follows:

cimport classes

cdef class BaseClass:
    cdef classes.BaseClass *wrapped

    def __cinit__(self, BaseClass parent=None):
        print "Cython: running BaseClass.__cinit__ with parent =", parent

        # Only continue if this is actually a BaseClass and not some sub-class.
        if type(self) != BaseClass:
            return

        # Create a C++ pointer to the base class, and initialise it to null.
        cdef classes.BaseClass *base = NULL

        # If we have a parent, retrieve the pointer to the wrapped base class.
        if parent is not None:
            base = parent.wrapped

        # Try to create an instance of the wrapped class.
        self.wrapped = new classes.BaseClass(base)

        # If we couldn't create one, we're out of memory.
        if self.wrapped is NULL:
            raise MemoryError()

    def __dealloc__(self):
        print "Cython: running BaseClass.__dealloc__ on", self
        if self.wrapped is not NULL:
            del self.wrapped
            self.wrapped = NULL


cdef class MainClass(BaseClass):
    def __cinit__(self, BaseClass parent=None):
        print "Cython: running MainClass.__cinit__ with parent =", parent

        # Only continue if this is actually a MainClass and not some sub-class.
        if type(self) != MainClass:
            return

        # Create a C pointer to the base class, and initialise it to null.
        cdef classes.BaseClass *base = NULL

        # If we have a parent, retrieve the pointer to the wrapped base class.
        if parent is not None:
            base = parent.wrapped

        # Create an instance of the wrapped class. We have to cast it to
        # BaseClass to store it in the wrapped attribute.
        self.wrapped = <classes.BaseClass *>new classes.MainClass(base)

        # If we couldn't create one, we're out of memory.
        if self.wrapped is NULL:
            raise MemoryError()

    def __dealloc__(self):
        print "Cython: running MainClass.__dealloc__ on", self

        # If we successfully created an instance, cast it to a MainClass and
        # destroy it.
        cdef classes.MainClass *temp
        if self.wrapped is not NULL:
            temp = <classes.MainClass *>self.wrapped
            del temp
            self.wrapped = NULL

    def accumulate(self, new_number):
        (<classes.MainClass *>self.wrapped).accumulate(new_number)

    property total:
        def __get__(self):
            return (<classes.MainClass *>self.wrapped).get_total()

Running this in a Python terminal then exhibits the correct behaviour:

>>> import wrapper
>>> mc = wrapper.MainClass()
Cython: running BaseClass.__cinit__ with parent = None
Cython: running MainClass.__cinit__ with parent = None
   C++: creating BaseClass at 0x9a0c648
   C++: creating MainClass at 0x9a0c648
>>> mc.accumulate(10)
>>> mc.accumulate(-3.1)
>>> mc.accumulate(27)
>>> mc.total
34
>>> quit()
Cython: running MainClass.__dealloc__ on <wrapper.MainClass object at 0xb78ba090>
   C++: destroying MainClass at 0x9a0c648
   C++: destroying BaseClass at 0x9a0c648
Cython: running BaseClass.__dealloc__ on <wrapper.MainClass object at 0xb78ba090>

(Note that the floating point number given to the second call of accumulate has been converted to an integer before being passed to the C++ call.)

Parent testing

At this stage, we should check we can pass parent objects in correctly:

>>> import wrapper
>>> bc1 = wrapper.BaseClass()
Cython: running BaseClass.__cinit__ with parent = None
   C++: creating BaseClass at 0x968a648
>>> mc1 = wrapper.MainClass(bc1)
Cython: running BaseClass.__cinit__ with parent = <wrapper.BaseClass object at 0xb77b5090>
Cython: running MainClass.__cinit__ with parent = <wrapper.BaseClass object at 0xb77b5090>
   C++: creating BaseClass at 0x968a860 with parent at 0x968a648
   C++: creating MainClass at 0x968a860 with parent at 0x968a648
>>> mc2 = wrapper.MainClass(mc1)
Cython: running BaseClass.__cinit__ with parent = <wrapper.MainClass object at 0xb77b5080>
Cython: running MainClass.__cinit__ with parent = <wrapper.MainClass object at 0xb77b5080>
   C++: creating BaseClass at 0x9685988 with parent at 0x968a860
   C++: creating MainClass at 0x9685988 with parent at 0x968a860
>>> bc2 = wrapper.BaseClass(mc2)
Cython: running BaseClass.__cinit__ with parent = <wrapper.MainClass object at 0xb77b50a0>
   C++: creating BaseClass at 0x9685a10 with parent at 0x9685988

Allowing Python sub-classes

At the moment, we have a one-to-one correspondence between our C++ classes and Python classes. But lets say a user wants to create a Python sub-class of MainClass in order to add a method to make it simpler to accumulate sequences:

>>> import wrapper
>>> class PythonClass(wrapper.MainClass):
...     def accumulate_sequence(self, sequence):
...         for value in sequence:
...             self.accumulate(value)
...
>>> pc = PythonClass()
Cython: running BaseClass.__cinit__ with parent = None
Cython: running MainClass.__cinit__ with parent = None
>>> pc.accumulate_sequence([10, -3.1, 27])
Segmentation fault

Due to our type checking, no C++ instance has been created and so a segmentation fault occurs when the accumulate() method tries to access one. If we want to allow Python sub-classing we need to remove the type checking. Instead, at the top of each constructor, we check if a C++ instance has already been created, and if so, destroy it:

# If we already have a C++ instance (i.e., this is a sub-class), delete it.
if self.wrapped is not NULL:
    del self.wrapped

We can then create a Python sub-class:

>>> import wrapper
>>> class PythonClass(wrapper.MainClass):
...     def accumulate_sequence(self, sequence):
...         for value in sequence:
...             self.accumulate(value)
...
>>> pc = PythonClass()
Cython: running BaseClass.__cinit__ with parent = None
   C++: creating BaseClass at 0x892b648
Cython: running MainClass.__cinit__ with parent = None
   C++: destroying BaseClass at 0x892b648
   C++: creating BaseClass at 0x892b648
   C++: creating MainClass at 0x892b648
>>> pc.accumulate_sequence([10, -3.1, 27])
>>> pc.total
34
>>> quit()
Cython: running MainClass.__dealloc__ on <__main__.PythonClass object at 0xb77cfc0c>
   C++: destroying MainClass at 0x892b648
   C++: destroying BaseClass at 0x892b648
Cython: running BaseClass.__dealloc__ on <__main__.PythonClass object at 0xb77cfc0c>

Of course, this is more inefficient than the type checking due to the need to create and destroy various C++ instances. At the end of the day, it is up to you whether the trade-off is worth it.


CategoryCythonDoc

Something went wrong with that request. Please try again.