Non multa sed multum

A Few Words on Polymorphism

Polymorphism is the ability to assign a pointer to a base class to an instance of one its derived class. When a method is invoked on that pointer, the derived implementation, if provided, of the method is called otherwise, the inherited method is. The following is an example of such feature.

It is implemented with a cost in term of memory and time. For each class a virtual method table is stored and a pointer to it is added to the definition (transparently, by the compiler) of each class containing a virtual method (or deriving from a class that contains a virtual method). The table in turn contains pointer to the actual implementation of the virtual methods for the derived class. So the compiler only knows the object through a pointer to its base class, but it can still generate the correct code since it can indirect the calls to overridden methods via the virtual method table, then lookup for the method in the table and finally call it. So polymorphism comes at a cost of storing a virtual method table for each class, a pointer to it in each instance of a polymorphic class and two level in indirection when calling a virtual method.
Another pitfall is that since the indirection is required, usually virtual methods cannot be in-lined.

Curious Recurring Template Pattern

Templates can mitigated performance problems of dynamic polymorphism via the so called static polymorphism, or simulated dynamic binding.
The idea is to inject the base class with information at compile time about the Derived class as in the following example:

This is possibly the simplest example of static polymorphism possible.
The base class offers a functionality that depends on a template parameter. This parameter is the type of the class that needs this functionality. The base class tails the functionality for the Derived class.

Base is a class that takes a template parameter: Derived which is used in a static_cast construct. It uses the information about the type of Derived in order to perform a cast at compile time. Derived is a class that inherits from Base meaning that Derived is also of type ` and consequently inheriting the interface method too. Since interface is called from an object of type Derived is it safe to cast this to Derived.

To better clarify this concept, the following is what Derived looks showing also the inherited method from Base.
From the following example, it is clear that there is nothing strange or funny in the definition of Derived, neither of the interface() method.

As a more complex and usefule example, consider that scenario in which we want to add the comparison feature to a certain set of objects, still, decoupling the code implementing that functionality from the classes themself and possibly reducing code duplication i.e. writing the logic of the comparison methods only once and using some knowledge about a common interface that all the object sharing the functionality exposes. The key point for this specific example is that all objects that should have the Comparable functionality need to expose the < operator or an equivalent method. This is all we need from within an object.

Armed with this, one can get equals and `notEquals` methods for free for any object that uses the `Comparable` interface and provides the `less` comparison method only. This approach uses `virtual` methods and we have seen, it comes with a cost attached to it.

Let's use CRTP to obtain the same effect, at compile time!

Comparable using the Curious Recurring Template Pattern

The following code implements the same Comparable functionionality described above but it does so without involving any virtual methods at all.

the type, Derived, of the object passed as a template parameter have Comparable as super-class.

Derived exposes the operator method.

It is safe to pass a Person_T when a Comparable& because Person_T is sub-class of Comparableand because the parameters are taken by reference (all references/pointers to derived types are converted implicitly to base objects references/pointers when necessary).
Plus, we can safely downcast from Comparable& to const Person_T& as the actual parameter passed to the operator== is a const reference to Person_T.
Thus when the compiler encounters d1 < d2 can simply emits the correct object code since at this stage it knows the definition of the operator< for Person_T.The comparison of two objects is quite an orthogonal functional that we have successfully distilled out of a specific class without any run-time cost.

since o1 and o2 would be sliced (via object slicing mechanism) and, moreover they would be new objects with no information about the additional methods and data of a `Person_T`. One can still static_cast to a Derived& but the result is undefinedbehaviour.

To give you an idea of the differences in performance the following is a super simple code that uses the two approaches described above to compare Person objects.

Save my name, e-mail, and website in this browser for the next time I comment.

Current ye@r *

Leave this field empty

About This Site

Non multa sed multum

Search

Random Quote

“The real problem in speech is not precise language. The problem is clear language. The desire is to have the idea clearly communicated to the other person. It is only necessary to be precise when there is some doubt as to the meaning of a phrase, and then the precision should be put in the place where the doubt exists. It is really quite impossible to say anything with absolute precision, unless that thing is so abstracted from the real world as to not represent any real thing.”by Richard Feynman