我有点困惑为什么奇怪的重复模板模式(CRTP)设计模式上有这么多“讨厌”,例如我正在阅读“游戏编程宝石3”,并且有一个名为autoLists的设计。这使用CRTP创建每种类型对象的数组。
我的问题:
为什么这是一件坏事?特别针对AutoLists的想法,但一般来说CRTP的答案就足够了。
我的目的是在实体组件系统中使用它,这样我就可以轻松地分离每种类型的组件。
答案 0 :(得分:8)
C ++中的继承有两个不同的目的:
Mixins(将 new ,drop-in行为添加到类中,不重复代码)。
在这种情况下,基类没有自己的意义 - 它的目的是支持新行为,而 不 用作所有的共同基类子类。
多态性(在基类中扩展已经声明的行为)。
在这种情况下,基类为所有子类yada yada提供了一个公共接口。
CRTP通常用于第一个目的,virtual
用于第二个目的
认识到两者之间的差异并不容易,需要一些练习。
有时,你可以用两者来实现同样的目的 - 区别仅在于“多态”是静态的(在编译时)还是动态的(在运行时)。
如果你不需要运行时多态,那么你通常会使用CRTP,因为它通常更快,因为编译器可以看到编译时发生了什么。
也就是说,CRTP的使用范围非常广泛,以至于我会犹豫不决地说它有“如此多的仇恨”。
答案 1 :(得分:3)
我已经在C ++,Java和C#以及来自"同事的反馈和#34;中广泛使用了CRTP及其一些变体。我可以告诉你一件事:许多人根本不理解它,并自动对这种过于复杂的废话充满敌意......
直到有人使用它几次,人们才真正发现很难看到它的好处 - 就像其他任何"复杂的" "新"他们看到的机制。
确实有时它被用在错误的地方,并且必须特别小心地使用细节 - 但这是任何重要工具的生命。就像多继承一样 - 很多人讨厌它。但你怎么能讨厌锤子呢?没有什么可恨的,只要正确地使用它并在真正有益的地方,不仅仅是因为你可以。
首先,重新考虑一下你是否真的需要使用它。模板基类是否真的需要知道确切的派生类型?虚拟成员还不够吗?没有它可以逃脱吗?您的案例有哪些好处?它会成为更高级别的代码"更短,更易读,更明显或更具可扩展性或更不容易出错?
在许多情况下,您会发现基础不需要知道确切的派生类型,您可以用更多的虚拟方法替换它。但这可能会使整个代码对于更多用户来说更加复杂。另一方面,对于CRTP,最终的机制更多......“自动化”,有时实际上并不是有益的。
对于实体类,通常CRTP的某些变体实际上有一个原因:如果你的基础暴露了一些返回"类似的"对象,你经常希望这些方法返回精炼的" MyObject *"不是" ObjectBase *",如果没有它,那很难实现。但是,真正的问题是:这些方法是否真的应该存在于实体的基础类中,而不是在工厂的“经理”中。或者' storagecontext'?
答案 2 :(得分: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