c++ 模板多态性可以代替OO多态性吗?

daupos2t  于 2023-01-28  发布在  其他
关注(0)|答案(3)|浏览(173)

我正在尝试将模板编程(以及将来的模板元编程)应用到现实世界的场景中,我发现了一个问题,那就是C++模板和多态性并不总是按照我想要的方式一起工作。
我的问题是,我尝试应用模板编程的方式是否不合适(我应该使用普通的老OOP),或者我是否仍然停留在OOP的思维模式中。
在这个特殊的例子中,我试图用strategy-pattern来解决一个问题,但我总是遇到这样的问题:我最终想要一些模板似乎不支持的多态行为。
使用组合的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代码可以轻松转换,基本上,用Template类型参数替换“接口”,并添加一个机制来传递Strategy/DataSource示例或配置参数。
但是当我深入到“main”时,我不清楚应该如何编写它来利用模板 meta编程风格中的模板。我经常想使用多态性,但它似乎并不适合模板(有时,感觉我需要Java的类型擦除泛型...唉)。
当我经常发现我想做的事情是有这样的TemplateType<?, ?> x = new TemplateType<X, Y>()其中x不关心X,Y是什么。
事实上,这经常是我在使用模板时遇到的问题。
1.我是否需要再应用一个级别的模板?
1.我是想用我闪亮的新电源模板扳手把OOP钉安装到PCI插槽中吗?
1.或者我只是认为这一切都错了,当谈到模板编程?
[编辑]一些人指出这实际上不是模板元编程,所以我稍微修改了一下这个问题。也许这就是问题的一部分--我还没有弄清楚TMP到底是什么。

jvidinwx

jvidinwx1#

模板提供静态多态性:在编译时指定一个模板参数来实现策略,但不提供动态多态性,即在运行时为对象提供实现策略的虚拟成员函数。
您的示例模板代码将创建三个不同的类,每个类包含所有的Interpolator代码,使用不同的模板参数编译,并可能从它们中内联代码。这可能不是您希望从代码大小的POV中得到的结果,尽管这没有什么绝对的错误。假设您正在进行优化以避免函数调用开销,那么它可能是对动态多态性的一种改进。更有可能是矫枉过正。如果你想动态地使用策略模式,那么你不需要模板,只要在相关的地方进行虚拟调用就行了。
不能有MyTemplate<?>类型的变量(除了在示例化之前出现在另一个模板中)。MyTemplate<X>MyTemplate<Y>是完全不相关的类(即使X和Y是相关的),如果它们是从相同的模板示例化的,那么它们可能恰好具有相似的函数(它们不一定是--其中一个可能是专门化)。即使它们是,如果模板参数包含在任何成员函数的签名中,那么这些函数是不相同的,所以从动态多态的Angular 来看,同一个模板的示例和任意两个类处于相同的位置--只有给它们一个公共基类,并带有一些虚成员函数,它们才能发挥作用。
因此,您可以定义一个公共基类:

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 {
    ...
};

现在,您可以使用模板根据编译时已知的内容创建不同类型的插值器(因此从内插器到策略的调用是非虚拟的),并且使用动态多态性来对它们进行相同的处理,即使直到运行时才知道需要哪一个(所以从客户端到插值器的调用是虚拟的)。您只需要记住这两种技术几乎是完全独立的,而在哪里使用它们的决定几乎是不相关的。
顺便说一句,这不是模板元编程,它只是使用模板。
编辑。至于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预处理器不是。在资源限制的情况下,你可以在编译时进行任意计算。避免在编译时知道输入的情况下的运行时开销。模板可以像函数式语言一样操作它们的模板参数,模板参数可以是整数或类型,也可以是函数,尽管它们不能在编译时被"调用",也可以是其他模板,尽管它们不能作为结构体的静态成员被"返回"。

dw1jzc5e

dw1jzc5e2#

我发现模板和多态性可以很好的结合在一起,在你的例子中,如果客户端代码不关心Interpolator使用了什么模板参数,那么就引入一个抽象基类作为模板的子类。例如:

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

我经常使用这个习惯用法,它很好地隐藏了客户端代码中的模板性内容。
注意,我不确定这种模板的使用是否真的是“元编程”,我通常把这个冠冕堂皇的术语保留给更复杂的编译时模板技巧的使用,特别是使用条件、递归定义等在编译时有效地计算东西。

ltskdhd1

ltskdhd13#

模板有时被称为静态(或编译时)多态,所以是的,它们有时可以用来代替OOP(动态)多态。当然,它需要在编译时而不是运行时确定类型,所以它不能完全代替动态多态。
当我经常发现我想做的是有这样的模板类型x =新模板类型()其中x不关心X,Y是什么。
是的,这是不可能的。你必须做一些类似于DoIt()函数的事情。通常,我认为这最终会是一个更干净的解决方案(你最终会得到更小的函数,每个函数只做一件事--通常是一件好事)。但是如果类型只在运行时确定(就像主函数的OOP版本中的i一样),那么模板就不能工作。
但是在这种情况下,我认为您的模板版本很好地解决了这个问题,并且它本身就是一个很好的解决方案(尽管正如onebyone提到的,它确实意味着所有三个模板的代码都被示例化了,这在某些情况下 * 可能 * 是一个问题)

相关问题