C ++ idiom for abstract base class methods without dynamic overhead?

In C ++, is there a way to have an “abstract” method of the base class (that is, declared and called from the base class, but implemented in subclasses) without declaring the method as virtual ?

This question, of course, applies only to cases where polymorphism is not needed (pointers / references to basic types have never been used). Consider the following:

 #define NO_OPT asm volatile (""); // to prevent some compiler optimization template<typename DerivedType> void doSomething(DerivedType& d) { d.foo(); } namespace test1 { struct Base { inline void foo() { // ... do common stuff pre-call ... foo_impl(); // ... do common stuff post-call ... } virtual void foo_impl() = 0; // the abstract method }; struct D1 : public Base { virtual void foo_impl() final { NO_OPT } }; struct D2 : public Base { virtual void foo_impl() final { NO_OPT } }; // Then the usage of D1, D2, ..., DN, could be as follows: void go() { D1 d1; doSomething(d1); D2 d2; doSomething(d2); for ( long i=0; i < 5000000000; i++ ) { // this loop takes [9] seconds doSomething(d2); } } } 

Note that in this case polymorphism is not necessary and that there are many optimization possibilities for the compiler.

However, I checked this code in the latest g++ (4.8.2) and clang (3.4) with -O3 optimizations turned on, including binding time (LTO), and it was significantly slower than the following: alternative implementation (using templates instead of the virtual method):

 namespace test2 { template<typename DerivedType> struct Base : public DerivedType // inheritance in reverse { inline void foo() { // ... do common stuff pre-call ... DerivedType::foo_impl(); // ... do common stuff post-call ... } }; struct D1 { void foo_impl() { NO_OPT } }; struct D2 { void foo_impl() { NO_OPT } }; void go() { Base<D1> d1; doSomething(d1); Base<D2> d2; doSomething(d2); for ( long i=0; i < 5000000000; i++ ) { // this loop takes [3] seconds doSomething(d2); } } } 

g++ and clang were remarkably consistent, each of which constituted optimized code that took 9 seconds to complete the test1 loop, but only 3 seconds to complete the test2 loop. Therefore, even if the test1 logic does not need to be dynamically dispatched, since all calls must be resolved at compile time, it is still much slower.

So, to repeat my question: If polymorphism is not needed, is there a way to achieve this “abstract method” behavior using convenient direct class inheritance (for example, in test1 ), but without degrading the performance of virtual functions (as achieved in test2 )?

+4
source share
3 answers

Why do you need to explicitly refer to support during the execution of a situation that you do not have, and do not want this to be considered simple? This also confuses people. Templates are the perfect tool for this job.

You need a common interface. Given your sample implementation of the template, you don’t even need any obvious connection between the types that it has, only they are.

The classic CRTP mode is

 template<class d> struct b { void foo() { bark(); static_cast<d*>(this)->foo_impl(); bite(); } }; struct d1 : b<d1> { void foo_impl() { fuzzbang("d1"); } }; struct d2 : b<d2> { void foo_impl() { fizzbuzz("d2"); } }; 

which people saw earlier, it’s idiomatic on request, but your

 class m1 { protected: void foo_impl() { f1("1"); } }; class m2 { protected: void foo_impl() { f2("2"); } }; template<class m> struct i : private m { void foo() { bark(); m::foo_impl(); bite(); } }; 

there is much to recommend it: there is no need for casting, and the intention will be as clear.


I don’t think that you can legally compare optimization results with code with built-in mutable calls with results on the same code without them. Try two implementations in your actual code. Performance for everything you do five billion times will almost certainly dominate cache effects, not team counts anyway.

+5
source

If a function has a polymorphic value (a reference or a pointer to a polymorphic type), and it calls the virtual function through this variable, the compiler must assume that it can be that class or any class derived from it. And since polymorphism does not allow the compiler to find out if there are classes on it, it must use a virtual dispatcher to call the function.

There is only one way to avoid using virtual dispatch when calling a virtual function through a polymorphic value: to call a specific function of the type directly, just like you did in your case # 2.

Where you put this call is up to you. You can put the call in foo , you can have an alternative function derived_foo that people use to call the version of the derived class, etc. However, you want to do this, you must end up calling the virtual function in a different way. This requires the use of a different code.

It is possible that if it is equipped with a final class polymorphic variable, the compiler can optimize all virtual calls to this variable into non-virtual dispatches. However, this is not required by standard. Each compiler may or may not implement this optimization, so if it does not become widespread, you cannot count on it.

This question is very similar to the XY issue. You are trying to solve a problem, and you settled on a base class with a virtual function, with a derived class that you will always use. This problem is best solved using CRTP , which does not use virtual functions at all.

+3
source

If you do not need dynamic polymorphism, then do not use the virtual in your code:

 #define NO_OPT asm volatile (""); // to prevent some compiler optimization template<typename T> void doSomething(T& d) { d.foo(); } namespace test1 { struct Base { protected: void prior() {} void after() {} }; struct D1 :public Base { void foo() { prior(); NO_OPT after(); } }; struct D2 :public Base { void foo() { prior(); NO_OPT after(); } }; void go() { D1 d1; doSomething(d1); D2 d2; doSomething(d2); for ( long i=0; i < 5000000000; i++ ) { doSomething(d2); } } } 
+1
source

Source: https://habr.com/ru/post/1497139/


All Articles