【发布时间】:2014-07-14 15:52:27
【问题描述】:
我需要实现对实现相同接口的对象向量的有效访问。到目前为止,我一直在使用虚函数的继承:接口被定义为具有纯虚函数的抽象类,并且每个对象类都实现了虚函数。对象向量只是抽象类上的指针向量(参见消息末尾的动态访问示例)。
我需要更快地访问对象集合。因为我在编译时知道所有可能的对象类,所以我使用 boost::variant 来实现对象集合(即 boost::variant 的向量)。我需要访客计划的额外定义才能通过集合。为了明确表示所有对象都实现相同的接口,我使用 CRTP 来获得静态继承:接口是 CRTP 抽象,并且每个对象类都派生自模板化的 CRTP 抽象类。
这里是一个 CRTP 实现的例子。该接口简单地定义了两个函数f() 和g(double)。有两个派生类 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 和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);
}
}
}
【问题讨论】:
-
“这里是使用虚继承的相同代码。” 你的意思是使用虚函数。虚拟继承有所不同:stackoverflow.com/questions/21558/…
-
我不太明白第一个代码示例中基类模板的原因是什么。
-
@dyp:感谢您的相关评论。因此,我更正了文本。CRTP 的使用是为了使界面明确。确实如您所说,在第一个代码示例中没有必要,因为对象之间的关系由访问者处理。我想利用 CRTP 明确描述接口来简化访问者方案这一事实。更准确地说,查看访问者代码,即使很冗长,也相当简单。我“感觉”应该有一种更简单的方法可以从 CRTP 中“自动”定义它们,但我找不到。
-
啊,我明白了。通过应用访问者,您可以从运行时擦除的类型恢复具体类型。也就是说, FOREACH 循环内的代码必须“支持”多种不同的类型。你需要重载才能做到这一点。在 C++1y 中,您可以使用多态 lambda,例如
apply_visitor([&](auto& x) { x.f(); x.g(argdouble); }, c)。在 C++03 和 C++11 中你运气不好,因为你不能定义本地模板。我认为可以使用虚函数而不会对性能产生太大影响,因为无论如何都需要一种间接方式。shared_ptr可能是罪魁祸首。 -
感谢您的回答。我尝试使用或不使用 boost::shared_ptr。没有显着差异。带有 gcc 4.6 的 Xeon 2.7GHz 的时序如下。对于 100 个元素的向量,如果使用虚函数(对于 boost::shared 或 c 指针),则为 0.3 微秒,对于带有 CRTP 的 boost::variant,则为 0(不可测量)。注意:0.3us 对我来说很重要,因为整个算法在同一台计算机上花费了大约 5us。
标签: c++ variant crtp visitor-pattern boost-foreach