I am new to the concept of a 'Curiously Recurring Template Pattern', and I'm reading about its potential use cases here.
In that article, the author describes a simple case where we have two or more classes with some generic functionality:
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() {...}
}
The author argues that instead of repeating the generic functionality in each class, we could instead use 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);
};
But what I don't understand is why we can't just use an abstract class for the generic functionality and inherit from it:
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;
};
I can't think of any benefits the CRTP method provides that the abstract class doesn't. Are there any benefits? The abstract class method seems simpler to me, and avoids the potential case of unhelpful compiler error messages when an invalid type (one that doesn't implement getValue()
or setValue()
) is passed as the template param in the CRTP method.
The are several advantages of using CRTP:
A suggestion to determine which idiom is best to use is to weigh the differences based on what is needed for a particular codebase.
Yet a single codebase can use both idioms and utilize their strengths into an integrated application. It's more about knowing where and when you need each type.
Here's a prime example of a proper way to look at code design practices: I'll use the idea of a simple 3D Graphics Engine to illustrate this method of thinking, planning and developing.
In a 3D Game Engine; you might have several container classes that will store all of the game's assets such as image files known as textures, fonts, sprites, models, shaders, audio and more. These types of classes that manage the functionality to open, read and parse these files and convert their information into your supported custom data structures would be CRTP at their core yet they can still share concepts of inheritance.
For example, the individual manager classes themselves would be designed in a CRTP manner to handle all of the file loading, creating, storing and cleanup of the memory of your custom objects yet the bulk of them could in themselves inherit from a Singleton Base class object that may or may not require the subclasses to have virtual functions. A class hierarchy might look like this:
Singleton
- An abstract base class the must be inherited from, each of it's derived types can be constructed once per application run. All of the following classes are derived from Singleton
.
AssetManager
- Manages the storing and clean up of internally stored objects either being a texture, font, gui, model, shader, or audio file...AudioManager
- Manages all of the functionality of audio playback.TextureManager
- Manages the instance count of different loaded textures, prevents unnecessary duplicate opening, reading, and loading a single file multiple types and prevents generating and storing multiple copies of the same object.FontManager
- Manages all of the fonts properties, similar to the TextureManager
but designed specifically for handling fonts.SpriteManager
- Depending on engine and game type sprites may or may not be used or could, in general, be considered a texture but of a specific type...ShaderManager
- Handles all of the shaders for performing lighting and shading operations within the generated frame or scene.GuiManager
- Handles all of the Graphical User Interface types of objects your Engine would support such as buttons, radio buttons, sliders, lists boxes, checkboxes, text fields, macro boxes, etc...AnimationManager
- Would handle and store all object animations your engine would support.TerrainManager
- Responsible for all of the games Terrain information from vertices and normal data to height maps, bump maps, etc, to colored or patterned textures, to various shaders that are associated with the terrain that can also include the skybox or skydome, clouds, sun or moon, and background information. May also include objects such as foilage - grass, trees, bushes, etc... ModelManager
- Handles all of the 3D model information generating the necessary 3D meshes for your objects and also handles other internal data such as texture coordinates, index coordinates, and normal coordinates.As you can see from above each of these manager classes would be designed with CRTP since it provides a way of creating generic structures that can process many different types of objects. Yet the entire class Hierarchy still uses inheritance and may or may not require virtual methods. The later depends on your needs or intent. If you are expecting someone else to reuse your Singleton
class to implement their own personal type of manager or some other class that would be a Singleton
such as a Logger
and or an ExceptionHandler
then you might want to require them to have to implement Initialization
and Cleanup
functions.
Now as for your in-game objects such as the models that would be used in your class or even abstract ideas such as a player and enemies where they would inherit from a general Character
class these would generate a class hierarchy that may or may not require the use of virtual methods depending on the particular needs and these classes would not require the CRTP idiom.
This was to give a demonstration on when, where and how to use these idioms properly.
If you want to see how the performance differs between the two, what you could end up doing is write two codebases that will perform the same exact task, create one specifically with CRTP and the other without it only using Inheritance and Virtual functions. Then enter those codebases into one of the various online compilers that will generate the different types of assembly instructions for the various types of available compilers that can be used and compare the generated assembly. I like Compiler Explorer that is used by Jason Turner.