Reputation: 304
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))
;
Upvotes: 2
Views: 1349
Reputation: 51871
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
Upvotes: 1