Joh*_*ien 8 c++ abstract-class crtp
我对“奇怪的重复模板模式”的概念很陌生,我正在此处阅读有关其潜在用例的信息。
在那篇文章中,作者描述了一个简单的情况,其中我们有两个或多个具有一些通用功能的类:
class A {
public:
int function getValue() {...}
void function setValue(int value) {...}
// Some maths functions
void scale() {...}
void square() {...}
void invert() {...}
}
class B {
public:
double function getValue() {...}
void function setValue(double value) {...}
// Some maths functions
void scale() {...}
void square() {...}
void invert() {...}
}
Run Code Online (Sandbox Code Playgroud)
作者认为,我们可以使用 CRTP,而不是在每个类中重复通用功能:
template <typename T>
struct NumericalFunctions
{
void scale(double multiplicator);
void square();
void invert();
};
class A : public NumericalFunctions<A>
{
public:
double getValue();
void setValue(double value);
};
Run Code Online (Sandbox Code Playgroud)
但我不明白的是为什么我们不能只使用抽象类来实现通用功能并从中继承:
class NumericalFunctions
{
virtual double function getValue() = 0;
virtual void function setValue(double value) = 0;
void scale(double multiplicator){...};
void square(){...};
void invert(){...};
};
class A : public NumericalFunctions
{
public:
double getValue() override;
void setValue(double value) override;
};
Run Code Online (Sandbox Code Playgroud)
我想不出 CRTP 方法提供了抽象类所没有的任何好处。有什么好处吗?抽象类方法对我来说似乎更简单,并且避免了当无效类型(未实现getValue()or 的setValue()类型)作为 CRTP 方法中的模板参数传递时出现无用的编译器错误消息的潜在情况。
使用CRTP有几个优点:
确定最好使用哪种习惯用法的建议是根据特定代码库的需要来权衡差异。
然而,单个代码库可以使用这两种习惯用法,并将它们的优势运用到集成应用程序中。更重要的是了解您何时何地需要每种类型。
这是查看代码设计实践的正确方法的一个主要示例:我将使用简单的 3D 图形引擎的想法来说明这种思考、规划和开发的方法。
在 3D 游戏引擎中;您可能有几个容器类,用于存储所有游戏资源,例如纹理、字体、精灵、模型、着色器、音频等图像文件。这些类型的类管理打开、读取和解析这些文件的功能,并将其信息转换为您支持的自定义数据结构,这就是CRTP,但它们仍然可以共享继承概念。
例如,各个管理器类本身将以CRTP方式设计,以处理自定义对象内存的所有文件加载、创建、存储和清理,但其中大部分本身可以从 Singleton 基类对象继承可能需要也可能不需要子类具有虚函数。类层次结构可能如下所示:
Singleton- 必须继承的抽象基类,它的每个派生类型都可以在每次应用程序运行时构造一次。以下所有类均派生自Singleton.
AssetManager- 管理内部存储对象的存储和清理,无论是纹理、字体、GUI、模型、着色器还是音频文件...AudioManager- 管理音频播放的所有功能。TextureManager- 管理不同加载纹理的实例计数,防止不必要的重复打开、读取和加载单个文件的多种类型,并防止生成和存储同一对象的多个副本。FontManager- 管理所有字体属性,类似于TextureManager但专门为处理字体而设计。SpriteManager- 根据引擎和游戏类型,精灵可能会或可能不会被使用,或者通常可以被视为特定类型的纹理......ShaderManager- 处理所有着色器,以在生成的帧或场景中执行照明和着色操作。GuiManager- 处理引擎支持的所有图形用户界面类型的对象,例如按钮、单选按钮、滑块、列表框、复选框、文本字段、宏框等...AnimationManager- 将处理和存储您的引擎支持的所有对象动画。TerrainManager- 负责所有游戏的地形信息,从顶点和法线数据到高度图、凹凸图等,到彩色或图案纹理,到与地形相关的各种着色器,还可以包括天空盒或穹顶、云、太阳或月亮,以及背景信息。还可能包括树叶等物体 - 草、树木、灌木丛等......ModelManager- 处理所有 3D 模型信息,为您的对象生成必要的 3D 网格,还处理其他内部数据,例如纹理坐标、索引坐标和法线坐标。正如您从上面看到的,每个管理器类都将使用CRTP进行设计,因为它提供了一种创建可以处理许多不同类型对象的通用结构的方法。然而,整个类层次结构仍然使用继承,并且可能需要也可能不需要虚拟方法。后者取决于您的需求或意图。如果您期望其他人重用您的Singleton类来实现他们自己的个人类型的管理器或其他一些类(例如SingletonaLogger和 或 an)ExceptionHandler,那么您可能需要要求他们必须实现Initialization和Cleanup功能。
现在,对于您的游戏中对象(例如将在您的类中使用的模型)甚至抽象概念(例如玩家和敌人),它们将从通用类继承,Character这些将生成一个类层次结构,该层次结构可能需要也可能不需要根据特定需要使用虚拟方法,并且这些类不需要CRTP习惯用法。
这是为了演示何时、何地以及如何正确使用这些习语。
如果您想了解两者之间的性能有何不同,您最终可以做的是编写两个执行相同任务的代码库,创建一个专门使用CRTP 的代码库,而另一个则仅使用继承和虚拟函数而不使用 CRTP。然后将这些代码库输入到各种在线编译器之一中,该编译器将为可使用的各种类型的可用编译器生成不同类型的汇编指令,并比较生成的程序集。我喜欢Jason Turner使用的Compiler Explorer。