1

I want to write a class A with two methods b and c. The method b calls c. I want to be able to replace c with anything I like.

I think I can do that if I make c a function pointer or virtual method.

However, b will be called for many times. Will these two solutions affect the performance?

Is it possible to redesign this to avoid using function pointers/virtual methods?

Test inheritance:

#include <iostream>
class A { 
public:
    A() {}
    void b() { c(); }
    void c() { std::cout << "1\n"; }
};

class B : public A { 
public:
    void c() { std::cout << "2\n"; }
};

int main() {
    B b;
    b.b();
}

Result:

1

Not 2.

Edit: I want to specify what c does at compile time. There are multiple forms of c that I want to plug into the same b method.

R zu
  • 2,034
  • 12
  • 30
  • `std::sort` can take a comparison function as an argument. Is that argument a function pointer too? – R zu Apr 10 '18 at 17:48
  • No, std::sort is a template function. It's argument could be a function pointer, but it could a be a functor or a lambda, and is resolved in compile time. – SergeyA Apr 10 '18 at 17:51
  • Maybe I could provide an additional template argument C. C can take in a class with static method. And `A::b()` would call `C::c()`. I remember google sparsehash takes a template argument like that for hash functions. – R zu Apr 10 '18 at 18:14
  • 1
    The bottom like is that if you want to have the flexible way to call different functions at runtime the compiler needs a flexible runtime mechanism to differentiate which function gets called. So no matter what, you are going to have to pay a penalty that is likely identical to a virtual call or a function pointer dereference. The compiler will probably do a better job in its virtual function table than you could cobble together trying to beat it. – Galik Apr 10 '18 at 18:21
  • Hmm. I am fine if the choice is determined at compile time. I guess I will test the class + static method way to see if it works. – R zu Apr 10 '18 at 20:27
  • I found another alternative: mixin. https://stackoverflow.com/questions/18773367/what-are-mixins-as-a-concept when I use crtp, the code has lots of `this->` or `Base::method()`... – R zu Apr 14 '18 at 16:14

4 Answers4

3

Both virtual functions and function pointers suffer from the same performance problems.

First, it is the general inability for compiler to inline those calls. Generally compiler will have to do real function calls and assume side effects, so a lot of optimization techniques can't be performed. Often the impact is quite significant.

Second, during code execution, CPU will generally not be able to prefetch code past the pointer call - since it won't know where the execution will be transferred to. However, hardware call devirtualizers, similar to branch predictors, try to speculatively prefetch instructions based on past performance, and after several calls they are trained and do a good job there. However, lack of inlining still bites you.

SergeyA
  • 61,605
  • 5
  • 78
  • 137
1

If you want no performance penalties, you could use CRTP:

template <typename Derived>
class A { 
public:
    A() {}
    void b() { static_cast<Derived*>(this)->c(); }
    void c() { std::cout << "1\n"; }
};

class B : public A<B> { 
public:
    void c() { std::cout << "2\n"; }
};

int main() {
    B b;
    b.b();
}

Live example: https://onlinegdb.com/SJzxbtqjz

However, this might not be what you want - it all depends on your use case, which I cannot tell from your question. Make sure you read the pitfalls section on the wikipedia page I linked above:

One issue with static polymorphism is that without using a general base class like "Shape" from the above example, derived classes cannot be stored homogeneously as each CRTP base class is a unique type. For this reason, it is more common to inherit from a shared base class with a virtual destructor, like the example above.

EDIT:

Otherwise, you'll need to do what you suggested, and pay the price of going through a function pointer. However, if the function you are calling is not something very simple, you probably won't see a difference in performance.

EDIT 2:

The compiler will completely inline the call toc() with appropriate optimizations.

GCC 7.3 with -O2 optimization flag, and a bit simpler code (just to avoid complexity of std::cout):

template <typename Derived>
class A { 
public:
    A() {}
    int b() { return static_cast<Derived*>(this)->c(); }
    int c() { return 1; }
};

class B : public A<B> { 
public:
    int c() { return 2; }
};


int f() {
    B b;
    return b.b();
}

will generate no code for class B, and generate the following assembly for f:

f():
  mov eax, 2
  ret

Live example: https://godbolt.org/g/Mh99bZ

gflegar
  • 1,583
  • 6
  • 22
0

Template solution:

#include <iostream>

struct X1 {
    static void c() { std::cout << "1\n"; }
};

struct X2 {
    static void c() { std::cout << "2\n"; }
};

template <class X>
class A {
public:
    A() {}
    void b() { X::c(); }
};

int main() {
    A<X1> a;
    a.b();
    A<X2> b;
    b.b();
}

Result:

1
2
R zu
  • 2,034
  • 12
  • 30
0

Solution with reference and template.

It is better than just template because the method c can use the attributes within the class X1.

#include <iostream>
class X1 {
public:
    X1(int v) { _v = v; }
    void c() { 
        std::cout << "X1: " << _v << "\n"; 
    }
    int _v;
};

template <typename X>
class A {
public:
    A(X& x) : _x(x) {}
    void b() { _x.c(); }
private:
    X& _x;
};
template <typename X>
A<X> make_A(X& x) { return A<X>(x); }
int main() {
    X1 x1(10);
    // A a1(x1);  ///< need c++17
    auto a = make_A(x1);  ///< works for c++11
    a.b();
}

Result:

X1: 10
R zu
  • 2,034
  • 12
  • 30