The are several advantages of using CRTP:
- Generic Code - Codebases such as container classes, lists, arrays, etc. Programs that are evaluated at compile time.
- Reusability - Being able to easily use existing code to create new code.
- Efficiency - At the core of a Generic Codebase: objects, function calls, their types, and even their values are determined at compile time instead of runtime, unlike virtual methods that require the runtime overhead from the compiler's and operating system's generated virtual function tables.
- Maintainability - Easier to maintain as there is generally less code to manage, unlike multiple inheritances within complex class hierarchies.
- Reliability - Knowing the code will work for different types not having to worry about the internal implementation.
- Readability - The intent of code is obvious without needing to worry about its internal structure.
Some of the disadvantages are found within their complexities:
- Syntax - The syntax is more difficult and a challenge to have it written correctly without any code smells.
- Compiler - Depending on the compiler some messages can be more cryptic or if you don't truly understand the compiler, generated assembly and what happens behind the scenes it can point you to the wrong line of code that is actually producing or invoking the error.
- Linker - There shouldn't be any concerns with the linker as long as the code compiles and objects are linked correctly.
- Debugger - Similar to the compiler the messages can be more cryptic looking and may point to the wrong location in code that is actually causing or triggering the error or exception.
- Readability - It can be a little tougher to read due to the complexity of the required syntax
A suggestion to determine which idiom is best to use is to weigh the differences based on what is needed for a particular codebase.
- If you need fast and efficient code that can easily be reused with minimal overhead then CRTP would be the better fit.
- If you need a stronger OOP codebase that utilizes class hierarchies with inheritance, polymorphism, and dynamic and heap memory usages then the later would be a proper fit.
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.
- Use CRTP to create custom containers, iterators, and algorithms that are generic, reusable, efficient and are generated at compile time that will store and do work on your custom objects.
- Use Class Hierarchies to create your custom objects, classes, structures, etc. that you will use throughout your codebase.
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.