I need to implement an efficient visit of a vector of objects implementing a same interface. Until now, I was using inheritence with virtual functons: the interface is defined as an abstract class with pure-virtual functions and each class of objects implements the virtual functions. The vector of objects is simply a vector of pointers on the abstract class (see the end of the message for an example of dynamic visit).
I need a faster visit of the object collection. Since I know at compilation time all the possible classes of object, I used boost::variant to implement the collection of objects (ie a vector of boost::variant). I need the extra definition of a visitor scheme to go through the collection. To make explicit that all the objects implement the same interface, I used a CRTP to obtain a static inheritence: the interface is a CRTP abstraction, and each class of object derivated from the templated CRTP abstract class.
Here is an example of the CRTP implementation. The interface simply defines two functions f()
and g(double)
. There is two derivated classes C1
and C2
implementing the interface (with identical behavior).
#include <vector>
#include <boost/foreach.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/variant.hpp>
namespace testVariantSimple
{
// Definition of the interface (abstract class).
template< typename C >
struct CBase
{
void f() { static_cast<C&>(*this).f(); }
int g(const double & x) { return static_cast<C&>(*this).g(x); }
};
// Definition of the first implementation.
struct C1 : public CBase<C1>
{
void f();
int g(const double & x);
};
void C1::f() { return ; }
int C1::g(const double & x) { return sizeof(x); }
// Definition of the second implementation.
struct C2 : public CBase<C2>
{
void f();
int g(const double & x);
};
void C2::f() { return ; }
int C2::g(const double & x) { return sizeof(x); }
// Definition of the visitor for the first function of the interface.
class f_visitor : public boost::static_visitor<int>
{
public:
template< typename C >
int operator()(CBase<C> &c ) const { c.f(); return 0; }
};
// Definition of the visitor for the second function of the interface.
struct g_visitor : public boost::static_visitor<int>
{
const double & x;
g_visitor( const double & x ) : x(x) {}
public:
template< typename C >
int operator()(CBase<C> & c) const { return c.g(x); }
};
// Example of use: construct a random collection and visit it.
void test(int nbSample)
{
typedef boost::variant<C1,C2> CV;
std::vector<CV> vec;
for( int i=0;i<nbSample;++i )
{
switch( std::rand() % 2 )
{
case 1: vec.push_back( C1() ); break;
case 2: vec.push_back( C2() ); break;
}
}
double argdouble;
BOOST_FOREACH(CV & c, vec)
{
boost::apply_visitor( f_visitor(), c );
g_visitor g(argdouble);
boost::apply_visitor( g, c );
}
}
}
This piece of code works and is 15 times more efficient than the code using dynamic inheritence (see the end of the message for the code using dynamics). The code is slightly more difficult to read for somebody not familiar with CRTP, but not more difficult to maintain or write. Since the interface is explicit with the CRTP, the visitor implementation are rather trivial, but verbose, painful to understand and to use.
My question is simple: is it possible to define the visitor automatically from the CRTP interface. I would like to avoid the extra definition of f_visitor
and g_visitor
, and to obtain a more readable look like that:
BOOST_FOREACH( CV & c, vec )
{
c.f();
c.g(argdouble);
}
Thanks for your help. For the interested reader, here is the same code using the virtual inheritence.
namespace testDynamicSimple
{
struct CBase
{
virtual void f() = 0;
virtual int g(const double & x) = 0;
};
struct C1 : public CBase
{
void f() {}
int g(const double & x) { return 1; }
};
struct C2 : public CBase
{
void f() {}
int g(const double & x) { return 2; }
};
bool test(int nbSample)
{
typedef boost::shared_ptr<CBase> CV;
std::vector<CV> vec;
for( int i=0;i<nbSample;++i )
{
switch( std::rand() % 5 )
{
case 1: vec.push_back( CV(new C1()) ); break;
case 2: vec.push_back( CV(new C2()) ); break;
}
}
double argdouble = 0.0;
BOOST_FOREACH( CV & c, vec)
{
c->f();
c->g(argdouble);
}
}
}