调用派生类的模板函数

时间:2011-05-17 05:44:28

标签: c++ templates derived boost-serialization

我在C ++中遇到问题,在调用派生类的函数的同时有一个指向基类的指针。

编辑: 一些答案提到了CRTP

但我的观点是我需要一个指向“Base *”类而不是“Base *”的指针,因为我不知道当前正在处理的类型(当前实例是从某种工厂创建的)

类:

class Base 
{
..
template<typename T>
func (T arg) { ... };
};

class Derived1 : public Base
{
...
template<typename T>
func (T arg) { ... };
};


class Derived1 : public Base
{
...
template<typename T>
func (T arg) { ... };
};

用法:

int main()
{
   Base* BasePtr = new Derived1();

   // The expected function to be called is Derived1::func<int>()
   BasePtr->func<int>();

   return 0; // :)
}

我无法使func成为虚拟,因为该语言不支持虚拟模板功能。

仅当类只有模板参数时才允许,但如果其中的函数具有模板参数则不允许。

我已经看到在Boost.Serialization中解决了类似的问题,但无法理解解决方案。

谢谢,

Koby Meir

3 个答案:

答案 0 :(得分:7)

实施Curiously recurring template pattern(CTRP)

插图:

template<typename D>
class Base 
{
public:
    template<typename T>
    void func (T arg) 
    {
         static_cast<D*>(this)->func(arg);
    }
};

class Derived1 : public Base<Derived1>
{
public:
    template<typename T>
    void func (T arg) { /*...*/ }
};


Base<Derived1> *basePtr = new Base<Derived1>();
basePtr->func(100);

答案 1 :(得分:6)

两个现有的解决方案交换动态多态性的静态多态性。如果没有关于手头问题的更多细节,就不可能知道这是否是一种有效的方法,因为它基本上打破了多态层次结构:CRTP没有单一的基类,而是它们的一族。你不能将Derived1Derived2的对象放在同一个容器中,因为它们是无关的...如果您只需要共享代码,那么这是一个很好的解决方案,但如果您需要动态多态,则不是。看看访客模式和双重发送类似问题。

如果你需要动态多态,你可以尝试实现双重调度(这很痛苦,但如果层次结构足够小,则可行。基本上创建两个不同的层次结构,一个以Base为根,另一个用作一些简短的手动调度程序。以Base为根的层次结构将具有虚方法apply,第二层次结构将为第一层次结构中的每个类型提供虚函数:

class Base;
class Derived1;  // inherits from Base, implements Visitor
class Derived2;  // inherits from either Base or Derived2
struct Visitor {
   virtual void visit( Base& ) = 0;     // manually unrolled for all types
   virtual void visit( Derived1& ) = 0;
   virtual void visit( Derived2& ) = 0;
};
struct Base {
   virtual void apply( Visitor& v ) {   // manually replicate this in Derived1, 2
      v.visit( *this );
   }
   template <typename T> void foo(T);   // implement 
};

template <typename T>
struct FooCaller : Visitor {
    T& ref_value;
    FooCaller( T& v ) : ref_value(v) {}
    template <typename U> void call_foo( U& o ) {
       o.foo(ref_value);
    }
    virtual void visit( Base & b )      { call_foo(b); }
    virtual void visit( Derived1 & d1 ) { call_foo(d1); }
    virtual void visit( Derived2 & d2 ) { call_foo(d2); } 
};

我使用的名称在访问者模式中很常见,这种方法与该模式非常相似(我不敢称之为访问者模式,但方法类似,所以我只是借用了命名约定)

用户代码类似于:

int main()                     // main returns int, not void!!!
{
   Base* BasePtr = new Derived1();
   int i = 5;
   FooCaller<int> c(i)
   BasePtr->apply(c);          // [1] magic happens here
}

通过从const-references的引用更改(如果可能)函数的参数,可以释放事先声明ic的要求。实际的神奇之处在于,在[1]中,C ++单一调度机制将调用调用Derived1::apply,因为这是BasePtr指向的对象的动态类型。此时它会将Visitor::visit( Derived1& )作为参数调用。这将再次通过单个调度机制发送到FooCaller<int>::visit( Derived1& ),此时两个对象都已解析为其静态类型。当FooCaller<int>::visit调用call_foo时,参数U被推断为Derived1,当它调用Derived1::foo时,参数被推断为int并且它最后调用Derived1::foo<int> ......虽然有几个循环和间接......

根据您的特定用例,这可能过于复杂(如果静态多态性如CRTP可行)或太难维护(如果层次结构很大:对于Base层次结构中的每个新元素,您将拥有更新Visitor层次结构中的所有类型,因此,如果您可以避免这种复杂性,那就完美了。但在某些情况下,你需要这个。

另请注意,这是最复杂的完全动态解决方案,中间还有其他选项,具体取决于您需要成为运行时多态的内容......可能是您的层次结构模拟短路访问者的情况,并且您只需要手动展开将在内部调度到模板的不同虚拟函数,在这种情况下,上述复杂性的一半将消失。

另请注意,这在C ++中非常不寻常,如果您解释手头的实际问题,可能会有更好的更简单的解决方案,您所说的是解决原始问题的要求:动态调度到模板。

答案 2 :(得分:3)

检查this,它将帮助您实施CRTP。