Search code examples
pythonc++inheritancevirtualcython

Multiple Inheritance/Virtual Functions


Using Cython (acting as an interface so I can call C++ based functions via Python), I am trying to wrap a complicated hierarchal structure of C++ classes, all of which eventually inherit from one base class. This singular base class has many virtual functions, whose implementation is different based on the derived class under consideration. Derived classes also have their own unique functions as well.

I am having a lot of trouble wrapping this via Cython, primarily because I can't find a way to re-define self.thisptr within each of the inherited classes to be of that specific type (rather than the type of the singular base class). I have also tried type-casting, as well as trying to delete the pointer and then re-initialize it, but it still thinks its pointing to the same base class. Example code is below:

inheritTest.pyx:

cdef extern from "BaseClass.h":
    cdef cppclass BaseClass:
        BaseClass() except +
        void SetName(string)
        float Evaluate(float)
        bool DataExists()

cdef extern from "DerivedClass.h":
    cdef cppclass DerivedClass(BaseClass):
        DerivedClass() except +
        void MyFunction()
        float Evaluate(float)
        bool DataExists()
        SetObject(BaseClass *)

cdef class PyBaseClass:
    cdef BaseClass *thisptr
    def __cinit__(self):
        self.thisptr = new BaseClass()
    def __dealloc__(self):
        del self.thisptr

cdef class PyDerivedClass(PyBaseClass):
    #This is one of the solutions that I have tried
    def __cinit__(self):
        if(self.thisptr):
            del self.thisptr
        self.thisptr = new DerivedClass()
    def __dealloc__(self):
        del self.thisptr
    def Evaluate(self, time):
        #I need to call the Derived implementation of this function, not Base
        return self.thisptr.Evaluate(self,time)
    def SetObject(self, PyBaseClass inputObject):
         #The issue here is that it looks for SetObject to be declared in BaseClass and spits an error otherwise. If I put a empty declaration, the complain is of ambiguous method overload
         self.thisptr.SetObject(inputObject.thisptr)

Also, when I call SetObject in my Python script, I want to be able to pass in a PyDerived Object as input, since it inherits from PyBase, shouldn't it just work? I tried it and the error is that:

Argument has incorrect type: expected PyBase, got PyDerived

Hopefully my questions make sense, please let me know if you require any additional information :)


Solution

  • Rather than deleting the old copies of self.thisptr, just make the constructor for the base class only initialize the pointer if the type of self is the base class.

    def __cinit__(self):
        if type(self) is PyBaseClass:
            self.thisptr = new BaseClass()
    

    Be sure you guard against deallocation the same way in the base class __dealloc__ method as well.

    Then, to avoid a bunch of unnecessary casts, in the subclass wrapper, make two different attributes, one storing the pointer to the c++ object cast to the type of the base class and the other storing the pointer to the c++ object with its original type. Something like this:

    cdef DerivedClass* derivedptr
    def __cinit__(self):
        self.derivedptr = self.thisptr = new DerivedClass()
    

    Then, when you need to access the methods of the derived class, use the derived pointer instead.

    Be sure that, when deallocating, you only delete the derived pointer.

    I got this idea from this discussion on the Cython Users mailing list.