同じインターフェイスを実装するオブジェクトのベクトルの効率的なアクセスを実装する必要があります。今までは、仮想関数で継承を使用していました。インターフェイスは、純粋仮想関数を持つ抽象クラスとして定義され、オブジェクトの各クラスは仮想関数を実装します。オブジェクトのベクトルは、単純に抽象クラスのポインターのベクトルです (動的な訪問の例については、メッセージの最後を参照してください)。
オブジェクト コレクションにすばやくアクセスする必要があります。私はコンパイル時にオブジェクトのすべての可能なクラスを知っているので、boost::variant を使用してオブジェクトのコレクション (つまり、boost::variant のベクトル) を実装しました。コレクションを通過するには、ビジター スキームの追加定義が必要です。すべてのオブジェクトが同じインターフェイスを実装していることを明示するために、CRTP を使用して静的継承を取得しました。インターフェイスは CRTP 抽象化であり、オブジェクトの各クラスはテンプレート化された CRTP 抽象クラスから派生しています。
CRTP 実装の例を次に示します。インターフェイスは、2 つの関数f()
と を定義するだけg(double)
です。2 つの派生クラスがあり、インターフェイスC1
をC2
実装しています (動作は同じです)。
#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 );
}
}
}
このコードは機能し、動的継承を使用するコードよりも 15 倍効率的です (動的を使用するコードについては、メッセージの最後を参照してください)。CRTP に慣れていない人にとっては、コードを読むのは少し難しくなりますが、維持したり書いたりするのは難しくありません。インターフェイスは CRTP で明示的であるため、ビジターの実装はかなり単純ですが、冗長であり、理解するのも使用するのも面倒です。
私の質問は簡単です: CRTP インターフェイスから自動的に訪問者を定義することは可能ですか? f_visitor
andの余分な定義を避け、次のg_visitor
ような読みやすい外観を得たいと思います。
BOOST_FOREACH( CV & c, vec )
{
c.f();
c.g(argdouble);
}
ご協力いただきありがとうございます。興味のある読者のために、仮想継承を使用した同じコードを次に示します。
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);
}
}
}