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

4

由于我之前的问题表述有些混乱,所以现在我想清楚地说明我的目标。

我拥有以下内容(暂时忽略继承关系,重点关注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" );
    }   
};

现在我可以引入任意新类,这些类根据重载数据函数执行不同的操作,我们将在接下来的内容中将其称为“访问器”:

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

目标是让这个最后的调用成功。我考虑过的方法(以及为什么不可行):
第一种选择:让所有的访问器都继承一个共同的基类,比如AccessorBase,然后在Base中编写。
virtual state( AccessorBase& a ) const = 0;

并在X中实现所需的代码(调用状态的语法略有不同,但这可以修复)。 问题在于,AccessorBase将需要为数据函数中第二个参数作为可能类型的每个虚拟函数。由于这些类型可以是用户定义的类(请参见类组合的情况,X具有Y作为数据成员),我不知道如何使此策略能够工作。

第二个选择:为每个Accessor在Base中创建一个虚拟函数。这违反了开/闭原则,因为添加新的Accessor类(比如TxtArchive)需要修改基类。

我理解为什么虚拟成员函数不能被模板化(不同编译单元中可能有不同的vtbls)。但是,对我来说似乎应该有一种解决办法...... Base知道它实际上是X类型,并且Accessor的类型始终是显式的,因此问题只是找到一种调用方式(对于类型为XmlArchive的Accessor):

state( xmlArchive, x ); //xmlArchive of type XmlArchive, x of type X

这将产生结果。

总结一下,我想要的呼叫是:

state( myParameters, *basePtr );

如果basePtr指向一个与调用兼容的函数模板的派生类,则成功,否则抛出异常。

似乎boost::serialize做了类似的事情,但我无法弄清楚它是如何实现的(可能是通过模板在C++中重新实现继承关系,我看到一个This()函数返回最终派生指针,但这真的很令人困惑...)

再次感谢您的帮助!

4个回答

1
你可以使用访问者模式来解决这个问题:
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);

如果您不喜欢在`IVisitor`中实现每个不同的`on_data`类型,可以使用`boost::any`,但依我的经验,该函数的实现所需的分支并不值得。

1

我相信你可以使用dynamic_cast或C-style cast来完成这个任务,只要你知道你有一个X对象,将Base*转换为X*并调用你的方法(在你的示例中应该是静态的)。如果你的继承链更深,并且在编译时不知道你是否有X、Y或Z,则仍然可以完成它,但你需要启用RTTI。

因此,总结一下:

X::state( myParameters, *(X*)(basePtr) );

或者如果您启用了运行时类型信息(RTTI):

X::state( myParameters, *dynamic_cast<X*>(basePtr) );

在 X、Y、Z 场景中,您需要三个分支 wth 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;
}

你明白了。


但正如我在帖子中所述,我会知道我有X,因此调用正确的函数的责任应该委托给派生类自己(X Y和Z或任何其他继承自Base的类),它知道自己的类型。 - StephQ
是的,这就是为什么我说你需要RTTI(或一些存储类型X、Y、Z的枚举字段在Base中)因为你需要根据对象的运行时类型采取不同的分支。你不能使用虚函数,这只能让你拥有某种形式的RTTI,或者可能会用functors来模拟一个虚函数。无论哪种方式,你或编译器都必须在Base中添加一些内容。 - Eloff

0
将友元声明添加到您的基类中,使其变为:
class Base 
{
   template<class A> friend void state( A& a, const X& x );
};

0

我不确定我完全理解你所问的参数,但我的蜘蛛感觉到你想要的是奇异递归模板模式的应用:

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 实例中调用,但是编译器会为每个实现数据的用户定义类型单独实例化它。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接