【问题标题】:C++ member function template in derived class, how to call it from base class?派生类中的C++成员函数模板,如何从基类中调用它?
【发布时间】:2010-05-26 14:56:13
【问题描述】:

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

我有(暂时忽略继承,专注于 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

目标是使最后一次调用成功。我考虑过的(以及为什么不可接受):

第一种选择:让所有的Accessor都继承自一个通用的基类,比如AccessorBase,然后写在Base中

virtual state( AccessorBase& a ) const = 0;

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

第二个选项:在 Base 中为每个 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() 函数返回最派生的指针但是它变得非常混乱......)

再次感谢您的帮助!

【问题讨论】:

    标签: c++ inheritance templates boost


    【解决方案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 - 但根据我的经验,实现该函数所需的分支并不值得。

    【讨论】:

      【解决方案2】:

      我相信你可以使用 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;
      }
      

      你明白了。

      【讨论】:

      • 但正如我在帖子中所说,我知道我有 X,因此调用正确函数的责任应该委托给派生类本身(XY 和 Z,或任何其他继承自Base),它知道自己的类型。
      • 是的,这就是为什么我说你需要 RTTI(或者 Base 中存储类型 X、Y、Z 的一些枚举字段),因为你需要根据不同的分支在对象的 runtime 类型上。您不能使用虚函数,这只会让您使用某种形式的 RTTI,或者可能使用仿函数模拟虚函数。无论哪种方式,您或编译器都必须向 Base 添加一些内容。
      【解决方案3】:

      不确定我是否完全理解您所要求的参数,但我的蜘蛛感觉表明您所追求的是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 实例调用,但由编译器为每个用户定义的类型实现数据单独实例化。

      【讨论】:

        【解决方案4】:

        将朋友声明添加到您的 Base 中,使其变为

        class Base 
        {
           template<class A> friend void state( A& a, const X& x );
        };
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2013-02-18
          • 1970-01-01
          • 1970-01-01
          • 2014-08-01
          • 2017-02-10
          • 2017-06-19
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多