3

我有点困惑为什么奇怪重复模板模式(CRTP)设计模式有这么多“仇恨”,例如我正在阅读“Game Programming Gems 3”,并且那里有一个名为 autoLists 的设计。这使用 CRTP 创建每种类型对象的数组。

我的问题:

为什么这是一件坏事?专门针对 AutoLists 的想法,但一般来说,关于 CRTP 的答案就足够了。

我的目的是在实体组件系统中使用它,以便我可以轻松地分离每种类型的组件。

4

3 回答 3

9

C++ 中的继承有两个不同的目的:

  1. Mixins(向类添加的、插入式行为,无需重复代码)。
    在这种情况下,基类本身没有什么意义——它的目的是支持新的行为,而不是用作所有子类之间的公共基类。

  2. 多态性(扩展基类中已经声明的行为)。
    在这个场景中,基类为所有子类提供了一个通用接口,yada yada。

CRTP 一般用于第一个目的,virtual用于第二个目的。
认识到两者之间的区别并不容易,需要一些练习。

有时,您可以使用两者来实现相同的目标——不同之处仅在于“多态性”是静态的(在编译时)还是动态的(在运行时)。
如果您不需要运行时多态性,那么您通常会使用 CRTP,因为它通常更快,因为编译器可以在编译时看到正在发生的事情。

也就是说,CRTP 被广泛使用,以至于我不敢说它上面有“太多的仇恨”。

于 2013-03-10T06:40:04.097 回答
3

我在 C++、Java 和 C# 中都广泛使用了 CRTP 及其一些变体,从“同事反馈”中我可以告诉你一件事:很多人根本不理解它,并自动对“如此过于复杂”产生敌意废话”。

在有人使用它几次之前,人们真的很难看到它的好处——就像他们看到的任何其他“复杂”“新”机制一样。

的确,有时它会被用在错误的地方,并且必须在使用时特别注意细节——但这就是任何重要工具的生命。就像多重继承一样——很多人讨厌它。但是你怎么能讨厌锤子呢?没有什么可恨的,只要在真正有益的地方正确使用它,而不仅仅是因为你可以。

首先,重新考虑是否真的需要使用它。模板基类真的需要知道确切的派生类型吗?虚拟会员还不够吗?没有它你能逃脱吗?你的情况有什么好处?它会使“更高级别的代码”更短、更易读、更明显或更可扩展或更不容易出错吗?

在很多情况下,你会发现基类不需要知道确切的派生类型,你可以用更多的几个虚方法来代替它。但这可能会使整体代码对更多用户来说更加复杂。另一方面,使用 CRTP,最终机制更 .. '自动',这有时实际上是无益的。

对于实体类,通常 CRTP 的某些变体实际上是有原因的:如果您的基础公开了一些返回“相似”对象的实用方法,您通常希望这些方法返回精炼的“MyObject*”而不是“ObjectBase*”,那就是没有它很难实现。但是,真正的问题是:这些方法真的应该在实体的基类中,而不是在“工厂”、“经理”或“存储上下文”中吗?

于 2013-03-10T01:02:54.143 回答
1

CRTP 对CRTP 类的使用引入了编译器无法检查的限制,即它可能不是类型安全的。以下面为例。

#include <iostream>
using namespace std;

class Base {
public:
    virtual ~Base() {}
    virtual Base *copy() = 0;
    virtual void SayHello() = 0;
};

template <typename Derived>
class BaseCopy: public Base {
public:
    virtual Base *copy()
    {
        return new Derived(static_cast<Derived const&>(*this));
    }
};

如果 Base 类的用户不知道限制使用并声明

class Bar: public BaseCopy<Bar> { public: void SayHello(void) { cout << "Hello, I am Bar\n";} };
class Foo: public BaseCopy<Bar> { public: void SayHello(void) { cout << "Hello, I am Foo\n";} };

int main(void)
{
    Foo *foo = new Foo;
    Base *foo2 = foo->copy(); // What is foo2?
    foo->SayHello();
    foo2->SayHello();
    delete foo2;
    delete foo;

    return 0;
}

用例如编译这个。克++

g++ -Wall -g main.cpp -o CRTP-test.exe

将毫无问题地编译,但调用foo->copy();将调用未定义的行为,因为结果将是从 Foo 构造的 Bar。

//jk

于 2013-03-10T00:22:27.817 回答