Some time ago, programmers approached me, recently started studying with ++ and accustomed to procedural programming, complaining that the mechanism for invoking virtual methods is “slowing down”. I was very surprised.
Let's remember how virtual methods work. If a class contains one or more virtual methods, the compiler for that class creates a virtual method table, and adds a virtual table pointer to the class itself. The compiler also generates code in the class constructor for initializing the virtual table pointer. The selection of the called virtual method is performed at the stage of program execution by selecting the address of the method from the created table.
Total we have the following additional costs:
')
1) Additional pointer in the class (pointer to the table of virtual methods);
2) Additional code in the class constructor (to initialize the virtual table pointer);
3) Additional code for each call to a virtual method (dereferencing a pointer to a virtual method table and searching the table for the desired virtual method address).
Fortunately, compilers now support such optimization as devirtualization. Its essence lies in the fact that the virtual method is called directly if the compiler knows exactly the type of the object being called and the virtual method table is not used. Such optimization appeared quite a long time ago. For example, for gcc - starting with version 4.7, for clang'a starting with version 3.8 (the -fstrict-vtable-pointers flag appeared).
But still, is it possible to use polymorphism without virtual functions in general? The answer is yes you can. So-called “strangely repeating pattern” (Curiously Recurring Template Pattern or CRTP) comes to the rescue. True, it will be a static polymorphism. It is different from the usual dynamic.
Let's look at an example of converting a class with virtual methods into a class with a template:
class IA { public: virtual void helloFunction() = 0; }; class B : public IA { public: void helloFunction(){ std::cout<< "Hello from B"; } };
Turns into:
template <typename T> class IA { public: void helloFunction(){ static_cast<T*>(this)->helloFunction(); } }; class B : public IA<B> { public: void helloFunction(){ std::cout<< "Hello from B"; } };
Appeal:
template <typename T> void sayHello(IA<T>* object) { object->helloFunction(); }
Class IA accepts a spawned class and casts a pointer to this on the spawned class. static_cast checks the cast at compile level, therefore, does not affect performance. Class B is generated from class IA, which in turn is templated with class B.
Additional costs - an additional pointer in the class, additional code in the class constructor, additional code with each call of the virtual method, as in the first case are absent. If your compiler does not support devirtualization optimization, then such code will run faster and take up less memory.
Thanks for attention.
I hope someone note will be useful.