Demonstration Code–mixing objects of different types in a Boost.python list
In Python, a list is allowed to contain objects of various types. Using Python’s introspection capabilities, it’s easy to process a mixed list because you can just test each list member to see what kind it is, using isinstance(objectA, TypeB). This creates a problem when the list is passed into C++, where arrays and containers are designed to hold objects of one type. I encountered this problem when writing a small module to parse mathematical expressions. The expression is entered as a string in Python, which is parsed and “compiled” into a stack of objects. The compiled stack is passed to C++ using boost.python, where it is processed when needed. The stack consists of two or more fundamentally different objects: numerical constants and operators. The evaluation routine pops an object. If it’s a numerical constant, the value is placed on the operand stack. If it’s an operator, the appropriate operation is performed on the values at the top of the operand stack. With help from Alex Mohr and Christopher Woods on the C++-sig mailing list, I developed a method that is illustrated in the example. In C++, define a polymorphic base class (has at least one virtual method) and several classes that are derived from the base class. These classes are exposed to Python using Boost.python so that objects can be created in Python. A list of mixed objects is then passed back to C++ for processing. When trying to access a member of the list, C++ doesn’t know what kind of object to expect, so we have to use the Boost.python function extract<>() (see previous post for an introduction to extract). Now we extract a pointer to the base class using:
object = extract <BaseClass*> (list[index]);
This works because every object in the list is derived from the base class. As shown in the example, I first tried to do this with references (to avoid the whole pointer mess) but it did not work, so it seems that pointers are unavoidable here.
It seems that this use of introspection is relatively rare in OOP, and is sometimes actively discouraged. However, for this type of problem it seems like the most logical design. Is there a different design pattern that would work better?