Often I prefer to use templates in C++ over polymorphism to avoid confining myself to any particular inheritance heirarchy when writing algorithms. The fact that templated code can run on any object that offers the right interface is a power that is greatly underestimated by C++ critics, in my opinion.
However, this design approach has its limitations, as I learned this week, when I needed to store a collection of objects and call the “render()” method on all of them. This sounds like a task for polymorphism! Of course, this would be easy if I had designed all of these objects to inherit from a “Renderable” base class, but, alas, they did not. The only thing they had in common is that they all implemented Renderable concept, i.e. they all had a method named “render()”. Luckily, there’s a way to solve this problem without retrofitting all of my classes to inherit from Renderable, using the power of Boost. First, lets review the traditional way to do this with inheritance, and then we’ll talk about how we can solve this without inheritance.
Lets say I have a collection of objects of different types, but they all share the fact that they be rendered. I would like to store and render them in a uniform way. For example:
RenderableA a; RenderableB b;
renderables.push_back(a); renderables.push_back(b); render_all(renderables); Using polymorphism and inheritance, it is straightforward:Abstract_renderable { virtual void render() = 0; }
RenderableA : public Abstract_renderable { virtual void render() { cout << "RenderableA" << endl; } }; Renderable B : public Abstract_renderable { virtual void render() { cout << "RenderableB" << endl; } }; void render_all(const std:vector renderables) { for(int i = 0; i < renderables.size(); i++) { renderables[i].render(); } }
This approach is concise and idiomatic, but it only accomodates classes that inherit from Abstract_renderable. Why is this problematic? There are a number of reasons. Maybe you’re working with classes from several different third-party libraries that you can’t edit. Maybe you want to operate on abstract types in the same way as classes (admittedly, this is less relevant for “renderables”). Or maybe there simply isn’t a natural way to force all your classes into a tree heirarchy.
But you can’t store an array of heterogeneous types that don’t share a common base class, right?? WRONG! With the power of boost::any, I can store an array of, well, anything:
RenderableA a; RenderableB b;
// this vector can store literally any type:
std::vector renderables;
renderables.push_back(a);
renderables.push_back(b);
...
Okay, great, I can store the objects, but how do I get them back out and call render() on them? Well, this is where things get tricky. Boost::any doesn’t store any type information (since in general this can’t be known at run-time), so in a typical usage of boost::any, you have to cast it back to its original type before you can use it. The problem is, when you have a vector of boost::any, it’s often difficult to keep track of the type of every single element (even deciding on how to repesent type at run-time is tricky). The solution I use is to create a wrapper class that stores the objects as well as a callback to the functionality you care about. This is easiest to explain using an example:
class Renderable_collection { template <class RenderableType> void push_back(const RenderableType& obj_in) { // step 1: copy into a smart pointer boost::shared_ptr obj(new RenderableType(obj_in)); // step 2: store object renderables_.push_back(obj); // step 3: "strip off" the render callback and store it separately callbacks_.push_back(boost::bind(&RenderableType::render, obj.get())); }
void render_all()
{ // todo
}
private:
std::vector<boost::function0> callbacks_;
std::vector renderables_;
};
In the first line, we copy the incoming object into a smart pointer and push it into a vector of boost::any. The reason for this is a bit subtle. Notice that we’re storing two vectors: one vector of callbacks (callbacks_), and one vector of objects (renderables_). The vector of callbacks will contain references to the elements in the object vector, so it’s essential that these references aren’t invalidated if the object vector is re-allocated during a resize. Storing pointers solves this problem, and using a smart pointer instead of a raw pointer avoids a memory leak when the object vector is destructed.
In the third line, we “strip off” the render() method of the newly-constructed object and store it in the callback vector. We accomplish this using boost::bind, which in this context is converting a routine that is called as a method (i.e. “obj.render();” ) into a routine that can be called as a function (i.e. “render();” ). It does this by storing a pointer to obj inside a function object, whose parentheses operator is overloaded to call the method on the object pointer (yep, magic). The first argument to boost:bind() is a pointer a function or method, and the remaining arguments are the parameters to pass to that function. For class methods, the first argument is always the “this” pointer to the object you want to call the method with. We store the result into a boost::function0 object, which is simply a wrapper that can store any function or functor that receives zero parameters and returns void. I recommend becoming familiar with boost::bind if you aren’t already. It’s extremely powerful, and it has been integrated into the newly-finalized C++11 standard.
Now that we’ve stored the object and the necessary callbacks, we can implement the render_all() method:
class Renderable_collection { template void push_back(const RenderableType& obj_in) { ... }
void render_all() const
{
for(int i = 0; i < callbacks_,size(); i++)
{
// call each render callback in sequence
callbacks_[i]();
}
}
private:
std::vector<boost::function0> callbacks_;
std::vector renderables_;
};
Thus, our Renderable_collection class provides a way to store arbitrary collections of heterogeneous types, which are related solely by the fact that they all contain a method named “render” that takes zero arguments. Furthermore, it allows us to call the render method of all stored objects, even though their types are no longer known.
Improvements
The approach above requires that all objects being stored are copy constructible. This is a problem if you want to store non-copy-constructible objects (std::cout, anyone?), or if copying is too expensive to be practical. The solution is simple: partially specialize the push_back() method to handle pointers specially.
class Renderable_collection { .... template void push_back(const boost::shared_ptr obj) { // step 1: skipped! // step 2: store object renderables_.push_back(obj); // step 3: "strip off" the render callback and store it separately callbacks_.push_back(boost::bind(&RenderableType::render, obj.get())); }
...
};
The example above specializes for smart pointers, but if you’re tied to raw pointers (yuck!) you can specialize for that, too. You can probably omit step 2 in that case, but leaving it in wouldn’t break anything.
The approach above assumes there’s only one method that matters: render(). For more complex concepts with additional methods of interest, this approach is easy to extend. You’ll need to repeat step 3 above for each method to “strip off” additional callbacks, and you’ll need a callback vector for each method of interest.
In this example, render() is a method (i.e. member function) that receives zero arguments and returns null. However, approach should work for any function with any number of arguments or return type. For functions with one or more arguments, you’ll need to use boost:bind’s “placeholder” objects to leave the arguments unbound until you call them. For example:
// step 3 callbacks_.push_back(boost::bind(RenderableType::print, obj.get(), boost::_1));
This call to boost::bind has an additional parameter: boost::_1. This is a “placeholder” that leaves this parameter unbound. The resulting callback will have a single input parameter corresponding to this placeholder.
Drawbacks
Although this is a nice approach, it has some significant drawbacks. One drawback is performance-related: all the extra indirection of usng shared_ptr’s, boost::bind, and boost::function will incur some slight overhead compared to using native pointers and virtual functions in a polymorphic approach.
However the biggest drawbacks is that it is impossible to do a deep copy of Renderable_collection, because the true types contained inside the boost::any objects is unknown. However, since we used smart pointers, shallow copies are safe and free of memory leaks.