[C++-sig] boost.python - C++ class overridden in python causes slicing
Jean-Sébastien Guay
jean-sebastien.guay at cm-labs.com
Tue Sep 8 05:10:11 CEST 2009
Hi all,
Thanks to previous help (a while ago) I was able to make considerable
progress wrapping a pretty complex library (OpenSceneGraph) with
boost.python. I've been working on this a bit more lately in my free
time, and I've been able to wrap pretty much everything I wanted to.
It's starting to look really good.
But I've got a problem overriding classes with python code. The
overridden method gets called, but when (from python code) it calls some
other C++ method using the arguments it was given, the arguments are
sliced to the base type.
I'll give some example code, but unfortunately I have to give a lot for
it to make sense... Sorry about that. The definitions below are just for
context:
class osg::Node
{
public:
//...
// does nothing in base class
virtual void traverse(NodeVisitor& nv) {}
// ...
};
class osg::Group
{
public:
//...
// overridden to traverse children
virtual void traverse(NodeVisitor& nv) {...}
// ...
};
class osg::NodeVisitor
{
public:
// ...
virtual void apply(osg::Node& node) { traverse(node); }
virtual void apply(osg::Group& node) {
apply(static_cast<Node&>(node); }
// ... versions for other subclasses of osg::Node and osg::Group
inline void traverse(osg::Node& node)
{
// ...
node.traverse(*this);
}
// ...
}
This is a classic visitor double-dispatch implementation, which in C++
is used like this:
class DerivedVisitor : public osg::NodeVisitor
{
public:
DerivedVisitor() :
osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN)
{}
// Override the version of apply you want to do the work.
void apply(osg::Node& node)
{
// ... Do something
// method must call traverse(node) to continue traversal.
traverse(node);
}
};
main()
{
osg::Group* g1 = new osg::Group;
g1->setName("g1");
osg::Group* g2 = new osg::Group;
g2->setName("g2");
g1->addChild(g2);
osg::Node* n = new osg::Node;
n->setName("n");
g2->addChild(n);
DerivedVisitor v;
g1->accept(v);
}
This will call apply(osg::Node&) 3 times, because the first two times,
traverse(node) will call osg::Group's version of traverse(NodeVisitor&)
which traverses its children. (note that NodeVisitor's default
implementation of apply(Group&) is to call apply(Node&) and the same for
all other subclasses which call their parent class's apply() )
Now, the problem I'm having with this in boost.python is that apply_Node
(which is the name I've given my wrapped apply(Node&) so that python
knows which version to call) is being called only once (at the
parent-most node). So it seems that it's osg::Node's version of
traverse(NodeVisitor&) that's being called since it's not traversing to
its children. I've got another example of this with NodeCallback, where
a method traverse(Node*) should traverse to its children, but it doesn't
and I can see that because if it did, a certain 3D model would get drawn
to the screen, but it doesn't (and it does if I remove my
python-overridden NodeCallback).
My wrapper code is mostly based on the example here:
http://wiki.python.org/moin/boost.python/OverridableVirtualFunctions
struct NodeVisitor_wrapper : public NodeVisitor
{
// NodeVisitor constructors storing initial self parameter
NodeVisitor_wrapper(PyObject *p,
NodeVisitor::TraversalMode tm =
NodeVisitor::TRAVERSE_NONE)
: NodeVisitor(tm), self(p) {}
NodeVisitor_wrapper(PyObject *p, NodeVisitor::VisitorType type,
NodeVisitor::TraversalMode tm =
NodeVisitor::TRAVERSE_NONE)
: NodeVisitor(type, tm), self(p) {}
// In case NodeVisitor is returned by-value from a wrapped function
NodeVisitor_wrapper(PyObject *p, const NodeVisitor& x)
: NodeVisitor(x), self(p) {}
// Override apply to call back into Python
void apply(Node& node)
{
//std::cout << "in apply(Node&)" << std::endl;
try {
//std::cout << "Calling override" << std::endl;
call_method<void>(self, "apply_Node", node);
}
// Catch boost::python exception, means method was not
// overridden in subclass.
catch (error_already_set) {
NodeVisitor::apply(node);
}
}
// Supplies the default implementation of apply
void default_apply_Node(NodeVisitor& self_, Node& node)
{
//std::cout << "in default_apply(Node&)" << std::endl;
self_.NodeVisitor::apply(node);
}
// Override apply to call back into Python
void apply(Group& node)
{
//std::cout << "in apply(Group&)" << std::endl;
try {
//std::cout << "Calling override" << std::endl;
call_method<void>(self, "apply_Group", node);
}
// Catch boost::python exception, means method was not
// overridden in subclass.
catch (error_already_set) {
NodeVisitor::apply(node);
}
}
// Supplies the default implementation of apply
void default_apply_Group(NodeVisitor& self_, Group& node)
{
//std::cout << "in default_apply(Group&)" << std::endl;
self_.NodeVisitor::apply(node);
}
private:
PyObject* self;
};
BOOST_PYTHON_MODULE(_osg)
{
// ...
{
scope in_NodeVisitor = class_<NodeVisitor, NodeVisitor_wrapper,
bases<Referenced>,
ref_ptr<NodeVisitor> >
("NodeVisitor")
.def(init<NodeVisitor::TraversalMode>())
.def(init<NodeVisitor::VisitorType,
NodeVisitor::TraversalMode>())
.def("traverse", &NodeVisitor::traverse)
.def("apply_Node", &NodeVisitor_wrapper::default_apply_Node)
.def("apply_Group",
&NodeVisitor_wrapper::default_apply_Group)
.add_property("traversalMode",
&NodeVisitor::getTraversalMode,
&NodeVisitor::setTraversalMode)
;
enum_<NodeVisitor::TraversalMode>("TraversalMode")
.value("TRAVERSE_NONE",
NodeVisitor::TRAVERSE_NONE)
.value("TRAVERSE_PARENTS",
NodeVisitor::TRAVERSE_PARENTS)
.value("TRAVERSE_ALL_CHILDREN",
NodeVisitor::TRAVERSE_ALL_CHILDREN)
.value("TRAVERSE_ACTIVE_CHILDREN",
NodeVisitor::TRAVERSE_ACTIVE_CHILDREN)
;
enum_<NodeVisitor::VisitorType>("VisitorType")
.value("NODE_VISITOR",
NodeVisitor::NODE_VISITOR)
.value("UPDATE_VISITOR",
NodeVisitor::UPDATE_VISITOR)
.value("EVENT_VISITOR",
NodeVisitor::EVENT_VISITOR)
.value("COLLECT_OCCLUDER_VISITOR",
NodeVisitor::COLLECT_OCCLUDER_VISITOR)
.value("CULL_VISITOR",
NodeVisitor::CULL_VISITOR)
;
}
// ...
}
Note a few things about this:
1. I couldn't use the method for wrapping a class which you want to
override in python code that's given here:
http://www.boost.org/doc/libs/1_40_0/libs/python/doc/tutorial/doc/html/python/exposing.html#python.class_virtual_functions
because I would always get crashes at the get_override() line. But the
above technique works well (apart from the slicing problem). I'm using
boost 1.35 if that helps explain that.
2. I'm using try { ... } catch (error_already_set) { ... } because in
the library, all the virtual methods are optional to override.
call_method() bombs if my python subclass didn't have the method
overridden, so that's the way I found to make that work. If there's a
better way (perhaps to check if the method was indeed overridden or not
in the subclass, which is that the get_override() line I referred to
above does I know) please let me know.
Finally, my python code is:
class DerivedVisitor(osg.NodeVisitor):
def __init__(self):
# call parent class constructor with argument, as in C++
osg.NodeVisitor.__init__(self,
osg.NodeVisitor.TraversalMode.TRAVERSE_ALL_CHILDREN)
def apply_Node(self, node):
print "python apply_Node - node name:", node.name
# call traverse(node) as in C++ to continue traversal.
self.traverse(node)
g1 = osg.Group()
g1.name = "g1"
g2 = osg.Group()
g2.name = "g2"
g1.addChild(g2)
n = osg.Node()
n.name = "n"
g2.addChild(n)
nv = DerivedVisitor()
g1.accept(nv)
Note that the code is quasi-identical to the C++ version, right down to
the need to call the base class constructor with the right traversal
mode. That's what I want.
Now, if I provide overridden versions of both apply_Node() and
apply_Group(), I get correct results, but not when I only override
apply_Node(). So it would seem the slicing happens when call_method
calls that method with the osg::Node& argument. Considering when I
override osg::NodeVisitor in C++ (and only override apply(Node&) ) it
works as it should, why does the boost.python version slice off the
derived parts of objects? I can see nowhere where objects are passed by
value, so (IIRC) no slicing should occur...
In fact, no pass-by-value could occur, because all osg::Referenced
subclasses (which includes osg::Node and osg::Group) have protected
destructors so they can't be constructed on the stack.
If it helps, here's the top part of my osg::Node and osg::Group wrappers:
using namespace osg;
namespace boost {
namespace python {
template <class T> struct pointee< ref_ptr<T> >
{
typedef T type;
};
}
}
BOOST_PYTHON_MODULE(_osg) // repeated just for context
{
class_<Referenced, ref_ptr<Referenced> >("Referenced")
;
{
scope in_Object = class_<Object, bases<Referenced>, ref_ptr<Object>,
boost::noncopyable >("Object", no_init)
// ...
;
// ...
}
{
scope in_Node = class_<Node, bases<Object>, ref_ptr<Node> >("Node")
// ...
;
// ...
}
class_<Group, bases<Node>, ref_ptr<Group> >("Group")
// ...
;
}
All this (in theory) works, in the sense that I can make hierarchies of
nodes, groups, geodes, geometry, etc. all in Python code, also assign
textures, start a viewer to see all that, etc. Apart from another
related problem (which I'll start a separate thread to get help for)
this is the only part that's giving me problems for now.
Any help would be appreciated. Sorry for the length of this message, I
wanted to be thorough so the problem would be clear (and perhaps the
solution easy to see?). I hope it's not something too simple though, or
I'll have wasted a lot of time and a lot of your time too...
Thanks in advance,
J-S
--
______________________________________________________
Jean-Sebastien Guay jean-sebastien.guay at cm-labs.com
http://www.cm-labs.com/
http://whitestar02.webhop.org/
More information about the Cplusplus-sig
mailing list