Benefits of CRTP over an abstract class?
Asked Answered
P

1

8

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.

Pollen answered 16/12, 2019 at 0:42 Comment(1)
With crtp you can call a function of the template parameter directly, resolved at compile time. With virtual functions there's overhead from virtual function tables etc.Bloomington
O
4

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.

Outcrop answered 16/12, 2019 at 2:52 Comment(7)
Hi, you mentioned, can easily be reused with minimal overhead then CRTP would be the better fit. What is the overhead in CRTP?Contiguity
Shouldn't this be an advantage of using CRTP -- Linker - There shouldn't be any concerns with the linker as long as the code compiles and objects are linked correctly?Contiguity
@Contiguity Well, I'm 100% self taught, never had any formal classes and I'm not exactly a specialized profession on these. I just understand the general concepts of them, and more or less just trying to help others explore their strengths and weaknesses through a practical use case example. The concept of using the game engine was just to show how one might consider using the different types of idioms... I learned about these concepts along the way when I taught myself how to build a working 3D Game Engine just sharing my own experience and how to look at something or to approach it...Outcrop
@Contiguity After going back and reading what I previously stated. That's the point, there's no runtime overhead with Templates as it is all determined at compile time. You'll have a longer compilation time but there is minimal runtime overhead reducing the amount of memory being used within the stack or heap when it comes to type deduction. The reason the linker doesn't matter at this point is that it only links objects from multiple translation units into a single executable binary. If templates compile correctly and all files are linked correctly then the linker did its job and goes out of scope.Outcrop
One disadvantage of CRTP is that you cannot define methods as const when there is a static cast being applied. This means that these methods will not be visible to const objects that implement the interface using CRTP.Systemize
There is a nice solution for this though on fluent.cppSystemize
@RenéHiemstra I appreciate the feedback. There might be a way to circumvent it in some cases. I think you can remove the const of an object to get at its raw pointer or base type. I remember reading something on those lines just don't remember all the details so I'm not 100% certain. If so you should then be able to perform a static cast then make it const again. Manipulation through templates. It may also be implementation defined by the compiler... again not 100% certain. This could also change within future releases of the C++ Standard and possible new language features.Outcrop

© 2022 - 2024 — McMap. All rights reserved.