派生类中的C ++成员函数模板,如何从基类中调用它?

时间:2010-05-26 14:56:14

标签: c++ inheritance templates boost

由于我的骄傲问题听起来令人困惑,我认为最好清楚说明我想要实现的目标。

我有(暂时忽略继承并专注于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()函数返回最派生的指针但是它变得非常令人困惑......)

提前再次感谢您的帮助!

4 个答案:

答案 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 );
};