何时选择基于模板策略的设计,而不是基于非模板继承的设计

When to prefer templated policy based design over non-templated inheritance based design

本文关键字:于非模 继承 策略 选择 何时 于模板      更新时间:2023-10-16

我试图理解在基于策略的设计中使用模板的真正需求。通过对c++中新的模板设计的研究,我发现基于策略的类设计是一种强烈推荐的设计方式,它允许您从策略类中"插入"不同的行为。下面是一个简单的例子(wiki的缩写):

template <typename LanguagePolicy>
class HelloWorld : private LanguagePolicy
{
    using LanguagePolicy::message;
public:
    // Behaviour method
    void run() const
    {
        // policy methods
        cout << message();
    }
};
class LanguagePolicyA
{
protected:
    std::string message() const
    {
        return "Hello, World!";
    }
};
//usage
HelloWorld<LanguagePolicyA> hello_worlda;
hello_worlda.run(); // prints "Hello, World!"

一个快速的分析表明,只是为了获得不同的可插入方法message(),我们从一个模板类型继承,该模板类型的定义可以由任何人提供(并在编译时识别)。

但是相同级别的抽象(和可配置的方法)可以在不使用模板化代码的情况下通过简单的旧式运行时多态实现,如下所示。

class HelloWorld
{
    LanguagePolicy *lp; //list of all plugable class
public:
    HelloWorld(LanguagePolicy *lpn) {
        lp = lpn;
    }
    // Behaviour method
    void run() const
    {
        // policy methods
        cout << lp->message();
    }
};
class LanguagePolicy
{
protected:
    virtual std::string message() const;
};
class LanguagePolicyA: LanguagePolicy
{
protected:
    std::string message() const
    {
        return "Hello, World!";
    }
};
//usage
HelloWorld helloworld(new LanguagePolicyA);
helloworld.run();

功能和抽象水平方面,我看不出这两种方法有多大区别(尽管第二种方法对LanguagePolicy有几行额外的代码,我认为其他用户需要知道界面;否则理解LanguagePolicy取决于文档)。但我确实认为后者是"干净的"(来自不太使用模板的人)。这是因为在我个人看来,非模板化类更易于查看和理解。一个非常好的例子是流行的库VTK(可视化工具包),它使用第二种方法解决了许多不同的问题。尽管没有广泛的文档VTK,我们大多数人-它的用户,可以看看它的类图(有时他们是相当大的),并推断类的行为;并在我们的应用程序中开发高度可配置和复杂的管道(不能将VTK成像为基于模板:))。与之相反的是像STL/BOOST这样的库,我认为任何人都不可能在不使用大量文档的情况下识别类的工作方式。

所以我的问题是,基于模板的策略设计真的优于基于虚拟继承的策略设计吗(仅在基于策略设计的场景中)?如果是,什么时候,为什么?

都是有效的结构化方式,这实际上取决于需求。例如

运行时与编译时多态性

你想/可以/必须在什么时候实现多态性?

虚拟调用的性能开销

模板生成没有间接指示的代码

类的实际用法。

当必须存储异构集合时,需要基类,因此必须使用继承。

一本非常好的关于基于策略的设计的书是《Modern c++ design》

我想这要看情况了。使用模板的一个可能的缺点是类型应该在编译时就知道:

HelloWorld<English> hw; // English is plugged at compile-time

在第二个示例中,使用指向基的指针,该指针可能指向各种派生类。它究竟指向什么并不需要在编译时知道,因此可以在运行时通过(用户)输入来确定。这种方法的一个可能的缺点是虚拟调用开销。在某些应用程序和某些平台上,这可能是不需要的。