可以使用模板多态来代替OO多态吗?

时间:2009-07-31 15:55:54

标签: c++ templates oop

我正试图将模板编程(以及在未来的某些方面,模板元编程)应用于实际场景。我发现的一个问题是C ++模板和多态并不总是以我想要的方式一起玩。

我的问题是,我尝试应用模板编程的方式是不合适的(我应该使用普通的旧OOP),或者我是否仍然坚持OOP思维模式。

在这种特殊情况下,我试图使用策略模式解决问题。我一直在遇到这样的问题,我最终希望某些模板似乎不支持多种形式。

使用组合的OOP代码:

class Interpolator {
   public:
     Interpolator(ICacheStrategy* const c, IDataSource* const d);
     Value GetValue(const double);
}

void main(...) {
    Interpolator* i;
    if(param==1)
       i = new Interpolator(new InMemoryStrategy(...), new TextFileDataSource(...));
    else if(param==2)
       i = new Interpolator(new InMemoryStrategy(...), new OdbcDataSource(...));
    else if(param==3)
       i = new Interpolator(new NoCachingStrategy(...), new RestDataSource(...));

    while(run) {
       double input = WaitForRequest();
       SendRequest( i->GetValue(input));
    }
}

潜在模板版本:

class Interpolator<class TCacheStrategy, class TDataSource> {
   public:
     Interpolator();
     Value GetValue(const double);               //may not be the best way but
     void ConfigCache(const& ConfigObject);      //just to illustrate Cache/DS         
     void ConfigDataSource(const& ConfigObject); //need to configured

}

//Possible way of doing main?
void main(...) {
    if(param==1)
       DoIt(Interpolator<InMemoryStrategy,TextFileDataSource>(),c,d);
    else if(param==2)
       DoIt(Interpolator<InMemoryStrategy,OdbcDataSource>(),c,d)
    else if(param==3)
       DoIt(Interpolator<NoCachingStrategy,RestDataSource>(),c,d)

}

template<class T>
void DoIt(const T&  t, ConfigObject c, ConfigObject d) {
   t.ConfigCache(c);
   t.ConfigDataSource(c);
   while(run) {
      double input = WaitForRequest();
      SendRequest( t.GetValue(input));
   }
}

当我尝试将OOP实现转换为基于模板的实现时,可以毫不费力地翻译Interpolator代码。基本上,将“interfaces”替换为Template类型参数,并添加一种机制来传递Strategy / DataSource实例或配置参数。

但是当我开始讨论“主要”时,我不清楚如何利用模板元编程风格的模板来编写代码。我经常想使用多态,但它似乎不适合模板(有时候,感觉我需要Java的类型擦除泛型......呃)。

当我经常发现我想做的事情是TemplateType<?,?> x = new TemplateType<X,Y>()之类的东西,其中x不关心X,Y是什么。

事实上,这在使用模板时经常是我的问题。

  1. 我是否需要再申请一个级别 模板?
  2. 我是否尝试使用闪亮的新型电源模板扳手 将OOP钉安装到PCI插槽中?
  3. 或者我只想到这一切 在模板方面错了 编程?
  4. [编辑]有些人指出这实际上不是模板元编程,所以我略微重写了这个问题。也许这就是问题的一部分 - 我还不知道TMP究竟是什么。

3 个答案:

答案 0 :(得分:24)

模板提供静态多态性:您在实现策略的编译时指定模板参数。它们不提供动态多态性,您可以在运行时为实现策略的虚拟成员函数提供对象。

您的示例模板代码将创建三个不同的类,每个类都包含所有Interpolator代码,使用不同的模板参数进行编译,并可能使用它们内联代码。这可能不是你想要的代码大小的POV,尽管它没有任何明显的错误。假设您正在优化以避免函数调用开销,那么它可能是对动态多态性的改进。更有可能是过度杀伤。如果您想动态使用策略模式,那么您不需要模板,只需在相关的位置进行虚拟调用。

您不能拥有MyTemplate<?>类型的变量(除非在实例化之前出现在另一个模板中)。 MyTemplate<X>MyTemplate<Y>是完全不相关的类(即使X和Y是相关的),如果它们是从同一个模板实例化的,它们可能恰好具有类似的功能(它们不需要是 - 一个可能是专业化)。即使它们是,如果模板参数涉及任何成员函数的签名,那么这些函数也不相同,它们只是具有相同的名称。因此,从动态多态性的POV来看,同一模板的实例与任何两个类都处于相同的位置 - 只有在为它们提供具有一些虚拟成员函数的公共基类时,它们才能起作用。

因此,您可以定义一个公共基类:

class InterpolatorInterface {
public:
    virtual Value GetValue(const double) = 0;
    virtual void ConfigCache(const& ConfigObject) = 0;
    virtual void ConfigDataSource(const& ConfigObject) = 0;
    virtual ~InterpolatorInterface() {}
};

然后:

template <typename TCacheStrategy, typename TDataSource>
class Interpolator: public InterpolatorInterface {
    ...
};

现在你正在使用模板根据编译时已知的内容创建不同类型的Interpolator(因此从内插器到策略的调用是非虚拟的),并且你使用动态多态来对它们进行相同处理即使您在运行时之前不知道您想要哪一个(因此从客户端到内插器的调用是虚拟的)。你必须要记住,这两种技术几乎都是完全独立的技术,而决定使用它们的方法几乎是无关的。

不过,这不是模板元编程,它只是使用模板。

编辑。至于TMP是什么,这是规范的介绍性例子:

#include <iostream>

template<int N>
struct Factorial {
    static const int value = N*Factorial<N-1>::value;
};

template<>
struct Factorial<0> {
    static const int value = 1;
};

int main() {
    std::cout << "12! = " << Factorial<12>::value << "\n";
}

观察那个12!已由编译器计算,并且是编译时常量。这很令人兴奋,因为事实证明C ++模板系统是图灵完备的编程语言,C预处理器不是。根据资源限制,您可以在编译时进行任意计算,避免在编译时知道输入的情况下的运行时开销。模板可以像操作函数一样操作模板参数,模板参数可以是整数或类型。或者函数,虽然那些在编译时不能被“调用”。或者其他模板,尽管那些模板不能作为结构的静态成员“返回”。

答案 1 :(得分:7)

我发现模板和多态性很好地工作。在您的示例中,如果客户端代码不关心正在使用的模板参数Interpolator,则引入模板子类的抽象基类。 E.g:

class Interpolator
{
public:
    virtual Value GetValue (const double) = 0;
};

template<class TCacheStrategy, class TDataSource>
class InterpolatorImpl : public Interpolator
{
public:
     InterpolatorImpl ();
     Value GetValue(const double);
};

void main()
{
    int param = 1;

    Interpolator* interpolator = 0;

    if (param==1)
        interpolator = new InterpolatorImpl<InMemoryStrategy,TextFileDataSource> ();
    else if (param==2)
        interpolator = new InterpolatorImpl<InMemoryStrategy,OdbcDataSource> ();
    else if (param==3)
        interpolator = new InterpolatorImpl<NoCachingStrategy,RestDataSource> ();

    while (true)
    {
        double input = WaitForRequest();
        SendRequest( interpolator->GetValue (input));
    }
}

我使用这个成语非常多。它非常好地隐藏了客户端代码中的模板内容。

注意,我不确定这种模板的使用真的是类“元编程”。我通常保留使用更复杂的编译时模板技巧的宏伟术语,尤其是使用条件,递归定义等来在编译时有效地计算内容。

答案 2 :(得分:6)

模板有时被称为静态(或编译时)多态,所以是的,它们有时可以用来代替OOP(动态)多态。当然,它需要在编译时确定类型,而不是运行时,因此它不能完全取代动态多态。

  

当我经常发现我想做的事情就像TemplateType x = new TemplateType(),其中x不关心X,Y是什么。

是的,那是不可能的。您必须执行类似于DoIt()函数的操作。通常情况下,我认为最终会得到一个更清晰的解决方案(最终你会得到更小的功能,每个功能只做一件事 - 通常是一件好事)。但是如果类型仅在运行时确定(与主函数的OOP版本中的i一样),那么模板将无效。

但在这种情况下,我认为您的模板版本可以很好地解决问题,并且本身就是一个很好的解决方案。 (尽管有人提到过,但它确实意味着所有三个模板都会实例化代码,可能在某些情况下会成为问题)