Strangely repeating pattern patterns can help us write shared functionality by combining patterns and inheritance
CRTP Abbreviations for Curiously Recurring Template Patterns, a name coined by James Or. Couplin In his 1995 paper. This is a specialization of base classes using derivative classes as template arguments. It looks like this:
This is a very common example that we can find online when we search for CRTP. However, it is not very clear what problem we want to solve with this expression just by looking at this example. I will cover the details in the following sections.
As you can guessS When we construct an object of a class
derived And call
derived.interface(), He prints “application”.
When a class derives from a base class, it will inherit functions of the base class. The derived class can then call
protected Functions as in the following example:
In the case of CRTP, it is the other way around, the base class calls functions in the derived class (see the previous example).
This is called inverse inheritance because in normal inheritance we extend the functionality of the base class, while in CRTP we actually extend the functionality of the class derived by inheritance from the base class. Hopefully this will become clearer in the following sections.
Why the name CRTP?
I think the name was just stuck and because it might be easier to remember it than “Limited Quantity F”. Jim Coplein used the name because he watched this pattern of combining patterns and inheritance on different occasions.
The most important thing we need to know when learning designs, patterns or dialects is what problems we can solve using them. Let’s see what CRTP can provide in more detail.
Adding common functions to departments
We all know we need to avoid duplicating our code, we do not want to keep writing identical functions over and over again in many different classes. With OOP the first thing that comes to mind is probably the use of polymorphism.
Let’s look at an example, suppose we want all our classes to be able to print their type name using
typeid Operator, we need a function called
PrintType(). Implementing this function in the base class will not work because it will print the base class type.
The code above will print “4Base” twice (with GCC). This is definitely not what we want. We can solve this problem with polymorphism, by performing
Now it works as expected, this code prints “5Test1” and “5 Test2” twice. Note that we can get the same results by using
One problem you may already know is that by making our functions virtual, there will be runtime costs due to dynamic launch using vtables. This may not be a problem for your app, as it depends on how you use the virtual functions, the environment in which your app operates, and so on. If you do not use virtual functions as often as in a loop, the cost may be negligible in modern processors. However, if the cost of using a dynamic launch is an issue for you, you can use CRTP.
This is what it looks like with CRTP:
This code prints the same results as the previous version and is also more compact because there is no need to write the application of
PrintType() In derived classes. There is no such thing Additional charge For dynamic launch because we do not use virtual functions in this version. Everything is solved during compilation.
You can see it
Test2 Not necessarily related as in the familiar concept of inheritance. We can see this as adding functionality, we want
Test2 To print the type, then we add this functionality by inheriting from
What about static polymorphism?
People talk about CRTP for static polymorphism, it can be confusing because I do not think CRTP is for that. As explained above, CRTP is designed to distribute common functionality to make our code more modular.
Indeed, we can use CRTP to apply static polymorphism to replace dynamic polymorphism, if we wish. In the following code, we use known
Animal Our sample class. With dynamic polymorphism using virtual functions, this is how it looks:
With CRTP, we can replace this application with:
If what we want is to make our code run faster, this could be a solution. But if what we want is to have a uniform interface that in this case is
MakeSound() Function, we should probably use duck typing while compiling as follows:
The compiler will throw an error if the object we are moving to
MakeSound() Does not apply
So yes, we can apply static polymorphism with CRTP, but we have to choose how to apply our lessons wisely depending on what we want to achieve, do we want to use dynamic polymorphism, static polymorphism with CRTP, duck compilation time, etc.
Now, let’s see why CRTP is possible, the syntax may be a little confusing when you first see it.
A lazy show in C ++
Let’s look again at our example above, for the sake of reading, here’s the same code.
When the compiler is invited
Base<Test1> In line 9,
Test1 Delivered as a template argument nonetheless
Test1 Is not a complete type at this time. But, we do not have a compilation error there because in C ++, member functions of class templates are only created if they are used / read. Maybe we can call it a lazy show.
In this example, the limb function
PrintType() Created only in line 14. At this point,
Test1 He’s a whole type and that’s why he works.
This is not true for member variables. The following code will give us a compilation error.
The only difference is in line 7, it will cause the next error.
error: ‘Base<Derived>::derived’ has incomplete type Derived derived;
I think we can see why, if the compiler does not know what
Derived That is, he does not know the memory layout of
Inheritance means extending the structure
When a class inherits from a base class, in addition to inheriting public and protected functions, it also expands the structure (memory) of the base class. If both the base classes and the derived classes have organ variables, as in the following example:
The structure of
Derived The class looks like this:
static_cast Download (We convert
interface()) In CRTP sure, because we know it for sure
Derived Is the only child of
If you want to know more about memory layout and object model in C ++ you can read my article below.
Templates allow the base class to know the type of derivative classes
Another important point in CRTP is that the basic class must know exactly what type of derivative class is, which is not known when we use a standard inheritance. Because we use templates, we can achieve this.
As stated above in our examples,
Test2 Not really related. They use the same class template to add functionality. If we imagine using class diagrams, we can see that these are two separate classes.
- When writing code, we should always try to avoid duplication by generating common functions
- We can do this in many correct ways like dynamic polymorphism (inheritance), CRTP, etc.
- We must choose a solution that suits our application needs by considering efficiency and scalability
- CRTP offers one way to write common functionality