C++ class inheritance

197 views
Skip to first unread message

Daniele Nicolodi

unread,
Oct 29, 2014, 9:03:37 AM10/29/14
to cython...@googlegroups.com
Hello,

I'm wrapping with Cython a vast C++ project where a large number of
classes inherit from a limited number of base classes. To avoid
repeating myself too much I would like to exposed the interface
described by the base classes using class inheritance in Cython as well.
However I haven't found a way to do that that does not requires some
ugly explicit type casting.

A simple example. Given the C++ class definitions:

class Base {
private:
std::string _name;
public:
Base(std::string n) : _name(n) { };
std::string name() { return _name; };
};

class Example : public Base {
public:
Example(std::string n) : Base(n) { };
int foo(int a, int b) { return (a + b); };
};

I can write the following Cython:

cdef extern from "example.h":
cppclass _Base "Base":
_Base(string n)
string name()
cppclass _Example "Example":
_Example(string n)
string name()
int foo(int a, int b)

cdef class Base:
cdef _Base *obj
def __cinit__(self, n):
self.obj = new _Base(n)
def name(self):
return self.obj.name()

cdef class Example:
cdef _Example *obj
def __cinit__(self, name):
self.obj = new _Example(name)
def name(self):
return self.obj.name()
def foo(self, a, b):
return self.obj.foo(a, b)

What I would like to do is to avoid to have to repeat the declaration of
the inherited methods. My solution is the following:

from libcpp.string cimport string

cdef extern from "example.h":
cppclass _Base "Base":
_Base(string n)
string name()
cppclass _Example "Example":
_Example(string n)
int foo(int a, int b)

cdef class Base:
cdef _Base *obj
def __cinit__(self, n):
self.obj = new _Base(n)
def name(self):
return self.obj.name()

cdef class Example(Base):
def __cinit__(self, name):
self.obj = <_Base *>(new _Example(name))
def foo(self, a, b):
return (<_Example *>self.obj).foo(a, b)

which works fine (as for as I can tell), but the explicit type castings
really look ugly. I don't have much C++ or Cython experience, there is a
better way of doing what I'm trying to do?

Thank you!

Cheers,
Daniele

Robert Bradshaw

unread,
Oct 29, 2014, 10:59:01 AM10/29/14
to cython...@googlegroups.com
Declare _Example to be a subclass of _Base, i.e.

cppclass _Example "Example" (_Base):
...

No no casts from _Example* to _Base* will be needed.

> cdef class Base:
> cdef _Base *obj
> def __cinit__(self, n):
> self.obj = new _Base(n)
> def name(self):
> return self.obj.name()
>
> cdef class Example(Base):
> def __cinit__(self, name):
> self.obj = <_Base *>(new _Example(name))
> def foo(self, a, b):
> return (<_Example *>self.obj).foo(a, b)
>
> which works fine (as for as I can tell), but the explicit type castings
> really look ugly. I don't have much C++ or Cython experience, there is a
> better way of doing what I'm trying to do?

There's not much you can do about foo, as we can't enforce the
invariant that obj is not set to point to something other than an
_Example. (A similar issue would arise creating wrapper classes in
C++.) What you could do, however, is provide a cdef _Example*
_obj_as_example() method that does the cast for you which might be
less ugly. Alternatively, you could add a parallel field that always
points to the same thing but has the narrower type, e.g.

cdef class Example(Base):
cdef _Example *example
def __cinit__(self, name):
self.obj = self.example = new _Example(name)

(Note also that as written, your code will leak the new _Base object
as __cinit__ methods for all superclasses are always called.)

- Robert

Daniele Nicolodi

unread,
Oct 29, 2014, 11:15:20 AM10/29/14
to cython...@googlegroups.com
Hello Robert,

thank you for the quick answer.

On 29/10/14 15:58, Robert Bradshaw wrote:
> Declare _Example to be a subclass of _Base, i.e.
>
> cppclass _Example "Example" (_Base):

This does not seem to work:

Error compiling Cython file:
------------------------------------------------------------
...
cdef extern from "example.h":
cppclass Base:
Base(string n)
string name()
cppclass Example(Base):
Example(string n)
^
------------------------------------------------------------

example.pyx:8:16: no matching function for call to Base::Base()

Here I removed the symbol renaming thinking that Cython in this way
would be less confused, but it didn't help. Am I doing something wrong,
or did I hit a bug (Cyrthon 0.20.1)?

> (Note also that as written, your code will leak the new _Base object
> as __cinit__ methods for all superclasses are always called.)

That's right. What is the idiomatic way to avoid this?

Cheers,
Daniele

Robert Bradshaw

unread,
Oct 29, 2014, 12:20:50 PM10/29/14
to cython...@googlegroups.com
On Wed, Oct 29, 2014 at 8:15 AM, Daniele Nicolodi <dan...@grinta.net> wrote:
> Hello Robert,
>
> thank you for the quick answer.
>
> On 29/10/14 15:58, Robert Bradshaw wrote:
>> Declare _Example to be a subclass of _Base, i.e.
>>
>> cppclass _Example "Example" (_Base):
>
> This does not seem to work:
>
> Error compiling Cython file:
> ------------------------------------------------------------
> ...
> cdef extern from "example.h":
> cppclass Base:
> Base(string n)
> string name()
> cppclass Example(Base):
> Example(string n)
> ^
> ------------------------------------------------------------
>
> example.pyx:8:16: no matching function for call to Base::Base()
>
> Here I removed the symbol renaming thinking that Cython in this way
> would be less confused, but it didn't help. Am I doing something wrong,
> or did I hit a bug (Cyrthon 0.20.1)?

This is a bug, and looking at the code I'm not sure what it's even
trying to accomplish there... I'll fix this.

>> (Note also that as written, your code will leak the new _Base object
>> as __cinit__ methods for all superclasses are always called.)
>
> That's right. What is the idiomatic way to avoid this?

I don't know if it's idiomatic, but you could guard assignment by

if type(self) is Base:
self.obj = new _Base()

Robert Bradshaw

unread,
Oct 29, 2014, 12:36:50 PM10/29/14
to cython...@googlegroups.com

Robert Bradshaw

unread,
Oct 29, 2014, 1:48:42 PM10/29/14
to cython...@googlegroups.com
On Wed, Oct 29, 2014 at 9:36 AM, Robert Bradshaw <robe...@gmail.com> wrote:
> Fixed at https://github.com/cython/cython/commit/e22c29ee379a1bbcd65d46cf15a8947cb6581eb6

Note that as a workaround you can declare (but not ever use) a fake
no-arg constructor.

Daniele Nicolodi

unread,
Oct 29, 2014, 1:55:00 PM10/29/14
to cython...@googlegroups.com
On 29/10/14 18:48, Robert Bradshaw wrote:
> On Wed, Oct 29, 2014 at 9:36 AM, Robert Bradshaw <robe...@gmail.com> wrote:
>> Fixed at https://github.com/cython/cython/commit/e22c29ee379a1bbcd65d46cf15a8947cb6581eb6
>
> Note that as a workaround you can declare (but not ever use) a fake
> no-arg constructor.

Thanks! That would have been a follow-up question :)

Cheers,
Daniele

Reply all
Reply to author
Forward
0 new messages