2

In a game I'm working on I have two C++ classes (ICollidable and Sprite) which both virtually inherit another C++ class called Object, for its properties Position and Size. I have these three classes exposed to python with boost python. The Player class constructor in python looks like this for example:

class Player(Game.ICollidable, Game.Sprite, Game.IKeyboardListener, Game.IEntity):
    def __init__(self):
        Game.IKeyboardListener.__init__(self)
        Game.IEntity.__init__(self)    
        Game.ICollidable.__init__(self)
        Game.Sprite.__init__(self)

The problem is that when the Sprite init comes before ICollidable's init the Sprite position doesn't work correctly, and I cant reposition the sprite on the screen. I think whats happening is that there are separate instances of the position from Sprite and ICollidable which is what I thought would happen before I made the ICollidable class.

I've tried a variety of things to try and solve this problem and scoured the internets to find solutions to a problem like this one but I've come up empty handed. Any advice, solutions or hints would be greatly appreciated.

Edit:

These are the python bindings to my C++ classes:

class_<Object>("GameObject")
    .def_readwrite("Pos", &Object::pos)
    .def_readwrite("Size",&Object::size)
;
class_<Sprite, bases<Object>>("Sprite", init<>())
    // Sprite(TexID, Pos)
    .def(init<GLuint, glm::vec2>())
    // ----------------------------
    .def_readwrite("TexId", &Sprite::texid)
;
class_<ICollidableWrap, bases<Object>,boost::noncopyable>("ICollidable", init<>())
    //.def("Pos", &ICollidable::Object::pos)
    .def("Test", pure_virtual(&ICollidable::Test))
    .def("OnCollision",pure_virtual(&ICollidable::OnCollision))
;
3
  • 1
    If usefull I will include the wrapper code that exposes my classes to python Commented Jan 19, 2014 at 20:04
  • My first guess is that you're passing the parameters by value instead of by reference, but it's just a guess, you should provide more info... Commented Jan 20, 2014 at 9:14
  • I've added in the boost python bindings in hopes it might provide helpful info needed to solve this problem. Commented Jan 21, 2014 at 1:06

1 Answer 1

1

While both languages provide a solution to the diamond problem, they use different approaches. C++ removes ambiguity with virtual inheritance, causing a single subobject to be shared. On the other hand, Python removes ambiguity with a monotonic superclass linearization lookup. With Python inheritance not causing C++ types to share a single subobject, Boost.Python would need to emulate C++ virtual inheritance when a Python class inherits from two exposed C++ classes that share a common virtual base. Unfortunately, as best as I can tell, Boost.Python does not support this case, as the types provided to boost::python::bases<...> are not checked for virtual inheritance.

While not scalable and a possible indicator that it may be worth examining composition-based solutions, one simple inheritance solution is to expose a CollidableSprite C++ class that inherits from ICollidableWrap and Sprite, then have the Python class inherit from CollidableSprite.


Here is a basic example where class C inherits from B and A that virtual inherit from V:

#include <boost/python.hpp>

/// @brief Mock up forming the following multiple inheritance
///        structure:
///
///            .-->[ V ]<--.
///            |           |
///          [ A ]       [ B ]
///            |           |
///            '---[ C ] --'
struct V
{
  V() : x(0) {}
  virtual ~V() {}
  unsigned int x;
};

struct A : virtual public V {};
struct B : virtual public V {};
struct C : public A, public B {};

/// @brief Functions that force an object's hierarchy chain to be
///        split, disambiguating access to V.x.
void print_a(A& a) { std::cout << a.x << std::endl; }
void print_b(B& b) { std::cout << b.x << std::endl; }

BOOST_PYTHON_MODULE(example)
{
  namespace python = boost::python;
  // Expose hierarchy.
  python::class_<V>("V", python::no_init)
    .def_readwrite("x", &V::x)
    ;
  python::class_<A, python::bases<V> >("A");
  python::class_<B, python::bases<V> >("B");
  python::class_<C, python::bases<A, B> >("C");

  // Expose helper functions.
  python::def("print_a", &print_a);
  python::def("print_b", &print_b);
}

Interactive usage:

>>> import example
>>> class PyC(example.A, example.B):
...     def __init__(self):
...         example.A.__init__(self)
...         example.B.__init__(self)
... 
>>> c = PyC()
>>> example.print_a(c)
0
>>> example.print_b(c)
0
>>> c.x = 42
>>> example.print_a(c)
0
>>> example.print_b(c)
42
>>> class PyC(example.C):
...     pass
... 
>>> c = PyC()
>>> example.print_a(c)
0
>>> example.print_b(c)
0
>>> c.x = 42
>>> example.print_a(c)
42
>>> example.print_b(c)
42
Sign up to request clarification or add additional context in comments.

1 Comment

Thank you for the answer, this is the solution I'll go with.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.