【问题标题】:When to prefer templated policy based design over non-templated inheritance based design何时更喜欢基于模板策略的设计而不是基于非模板继承的设计
【发布时间】:2015-08-16 07:04:19
【问题描述】:

我试图了解使用模板进行基于策略的设计的真正要求。通过 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 这样的库,我认为任何人都不可能在不使用大量文档的情况下识别类的工作。

所以我的问题是,基于模板的策略设计真的比基于虚拟继承的更好吗(仅在这种基于策略的设计场景中)?如果是这样,何时以及为什么?

【问题讨论】:

    标签: c++ templates inheritance polymorphism policy


    【解决方案1】:

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

    运行时与编译时多态性。

    您希望/可以/必须何时实现多态性?

    虚拟调用的性能开销

    模板生成没有间接的代码

    类的实际用法。

    当你必须存储异构集合时,需要一个基类,所以你必须使用继承。

    Modern C++ Design

    是一本关于基于策略的设计的非常好的书(有点过时但还不错)

    【讨论】:

    • -> 模板通过在编译时生成类来“模拟”运行时聚合。那么,为什么不直接使用运行时呢?我们为什么关心? -> 我会看看,但虚拟调用的开销到底是多少?是否足以产生影响?
    • @krips89 第二点。虚拟调用会产生性能开销,这在某些情况下可能很重要。
    • @krips89 这取决于但如果您考虑到间接性、缓存行的混乱、内联模板函数的可能性,使用策略可能会有所收获。
    • 是的,我只看那本书;但他声称基于模板的方法是所有设计中最好的,我无法获得任何证据支持。他确实讨论了大多数方法的优缺点(模板有很多缺点),但继续推动模板化实现。
    • @krips89 快速基准测试(我想知道实际差异)ideone.com/LIpkqC
    【解决方案2】:

    取决于我猜的情况...使用模板的一个可能缺点是在编译时应该知道类型:

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

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

    【讨论】:

    • 是的;据我到目前为止所了解的,第二种方法中出现的唯一问题是“虚拟调用开销”。如果我纯粹从设计角度来看(不关心实现开销),第二种方法会更好。
    • 这是我现在能想到的唯一缺点。最重要的是,抽象基有助于强制派生类上的接口,这很好。当概念被添加到语言中时,静态接口检查可能会在 C++17 中可用(太棒了)。
    猜你喜欢
    • 1970-01-01
    • 2011-01-24
    • 1970-01-01
    • 1970-01-01
    • 2015-07-17
    • 1970-01-01
    • 2011-04-08
    • 2016-02-20
    • 2012-11-16
    相关资源
    最近更新 更多