由于我的骄傲问题听起来令人困惑,我认为最好清楚说明我想要实现的目标。
我有(暂时忽略继承并专注于X):
class Base {};
class X : public Base {
private:
double m_double;
public:
template<class A> friend
void state( A& a, const X& x ) {
data( a, x.m_double, "m_double" );
}
};
我现在可以根据数据函数的重载方式引入执行不同操作的任意新类,我们将在下面将它们称为Accessors:
class XmlArchive {...}; //One Accessor
template<class T>
void data( XmlArchive& a, const T& t, const std::string& str ) {
//read data and serialize it to xml Archive
}
class ParameterList {...}; //Another Accessor
template<class T>
void data( ParameterList& a, const T& t, const std::string& str ) {
//put data in a parameter list
}
然后我可以写:
X myX;
XmlArchive myArchive;
state( myArchive, myX );
ParameterList myParameters;
state( myParameters, myX );
棒极了,代码重用! :D但是以下(显然)失败了:
Base* basePtr = new X; //This would come from factory really, I should not know the type X
state( myParameters, *basePtr ); //Error
目标是成功完成最后一次通话。我考虑过(以及为什么不可接受):
第一个选项:让所有Accessor继承自公共基类,比如AccessorBase,然后写入Base
virtual state( AccessorBase& a ) const = 0;
并在X中实现所需的代码(调用状态的语法略有不同,但这可以修复)。 问题是AccessorBase需要为每个可能的类型提供一个虚函数,该函数作为数据函数中的第二个参数。由于这些类型可以是用户定义的类(参见类组成的情况,X具有Y作为数据成员),我不知道如何使这个策略可以工作。
第二个选项:在Base中为每个Accessor创建一个虚函数。这违反了开放/关闭原则,因为添加新的Accessor类(比如TxtArchive)将需要修改基类。
我理解为什么不能模板化虚拟成员函数(在不同的编译单元中可能有不同的vtbls)。 然而,它告诉我应该有这个问题的解决方案... Base知道它确实是X类型,并且Accessor的类型总是显式的,所以这是一个找到一种调用方式的问题(对于XmlArchive类型的访问者:
state( xmlArchive, x ); //xmlArchive of type XmlArchive, x of type X
将产生结果。
总结我想要电话:state( myParameters, *basePtr );
如果basePtr指向一个派生类,其函数模板与该调用兼容并且抛出异常,则成功。
我觉得boost :: serialize做了类似的事情,但我无法弄清楚如何(它可能是通过模板在C ++中重新实现继承关系,我看到一个This()函数返回最派生的指针但是它变得非常令人困惑......)
提前再次感谢您的帮助!
答案 0 :(得分:1)
我相信你可以使用dynamic_cast或C风格的强制转换来做到这一点,只要你知道你有一个X的对象,将Base *强制转换为X *并调用你的方法(在你的例子中它应该是静态的。)如果您的继承链更深,并且您不知道在编译时是否有X,Y或Z,那么您仍然可以执行此操作,但您需要启用RTTI。
总结一下:
X::state( myParameters, *(X*)(basePtr) );
或者如果您启用了RTTI:
X::state( myParameters, *dynamic_cast<X*>(basePtr) );
在X,Y,Z场景中,你需要三个分支,分别是Z :: state,Y :: state和X :: state,根据basePtr的运行时类型调用正确的分支。
switch(basePtr->get_type())
{
case TYPE_X:
X::state( myParameters, *(X*)(basePtr) );
break;
case TYPE_Y:
Y::state( ... );
break;
}
你明白了。
答案 1 :(得分:1)
您可以使用vistor模式:
class IVisitor
{
public:
virtual void on_data( double, const char * )=0;
virtual void on_data( std::string, const char * )=0;
};
class Base
{
public:
virtual void visit( IVisitor * v )=0;
};
class X : public Base
{
private:
double m_double;
std::string m_string;
public:
void visit( IVisitor * v)
{
v->on_data( m_double, "m_double" );
v->on_data( m_string, "m_string" );
}
};
Base * base = ...;
XmlArchive ar; // This must derive from IVisitor
base->visit(&ar);
ParameterList pl; // This must derive from IVisitor
base->visit(pl);
如果您不想在on_data
中实现每种不同的IVisitor
类型,您可以使用boost::any
- 但根据我的经验,在实现中需要分支功能不值得。
答案 2 :(得分:0)
我不确定我是否完全理解你所要求的参数,但我的蜘蛛感觉表明你所追求的是Curiously Recurring Template Pattern的应用:
class StateBase {
virtual void state() = 0;
};
template<typename T>
class StateMixin : public StateBase {
void state() {
T::data();
}
};
class MyType : public StateMixin<MyType>
{
void data() {};
}
这里,state()是使用StateMixin定义的,可以从任何StateBase实例调用,但是由编译器为每个实现数据的用户定义类型单独实例化。
答案 3 :(得分:0)
将好友声明添加到您的Base,使其成为
class Base
{
template<class A> friend void state( A& a, const X& x );
};