-1

I am trying to optimize the run time of my code and I was told that removing unnecessary virtual functions was the way to go. With that in mind I would still like to use inheritance to avoid unnecessary code bloat. I thought that if I simply redefined the functions I wanted and initialized different variable values I could get by with just downcasting to my derived class whenever I needed derived class specific behavior.

So I need a variable that identifies the type of class that I am dealing with so I can use a switch statement to downcast properly. I am using the following code to test this approach:

Classes.h

#pragma once

class A {
public:
    int type;
    static const int GetType() { return 0; }
    A() : type(0) {}
};

class B : public A {
public:
    int type;
    static const int GetType() { return 1; }
    B() : {type = 1}
};

Main.cpp

#include "Classes.h"
#include <iostream>
using std::cout;
using std::endl;
using std::getchar;

int main() {
    A *a = new B();
    cout << a->GetType() << endl;
    cout << a->type;
    getchar();
    return 0;
}

I get the output expected: 0 1

Question 1: Is there a better way to store type so that I do not need to waste memory for each instance of the object created (like the static keyword would allow)?

Question 2: Would it be more effective to put the switch statement in the function to decide that it should do based on the type value, or switch statement -> downcast then use a derived class specific function.

Question 3: Is there a better way to handle this that I am entirely overlooking that does not use virtual functions? For Example, should I just create an entirely new class that has many of the same variables

Ryoku
  • 397
  • 2
  • 16
  • I think you need to check out [CRTP](https://stackoverflow.com/questions/4173254/what-is-the-curiously-recurring-template-pattern-crtp). – Mark Ransom Jan 31 '18 at 20:36
  • 7
    "I am trying to optimize the run time of my code and I was told that removing unnecessary virtual functions was the way to go." Suspect advice, if you ask me. – Fred Larson Jan 31 '18 at 20:36
  • 1
    Have you profiled your code? Are you sure that virtual functions are the bottleneck indeed? – HolyBlackCat Jan 31 '18 at 20:39
  • @MarkRansom I heard that using templates would cause extreme bloat, is that true? From my understanding, every time I would instantiate a class using the template it would duplicate the class code. In my mind this means that the i-cache would suffer, but I may be thinking about this wrong – Ryoku Jan 31 '18 at 20:41
  • 1
    Don't worry about the memory required for an extra `int`, it's the same or less than would be taken by a vptr if you had a virtual function. – Mark Ransom Jan 31 '18 at 20:41
  • vtable calls are not that much more expensive than static calls. Real performance gains are seen through aggressive inlining which does preclude virtual calls, but it doesn't mean a static call will necessarily be much faster or inlined: https://eli.thegreenplace.net/2013/12/05/the-cost-of-dynamic-virtual-calls-vs-static-crtp-dispatch-in-c – Dai Jan 31 '18 at 20:41
  • 1
    Templates *can* lead to bloat, if your compiler is poor and can't optimize properly. Ideally only one copy of each member function for each template type will be generated. – Mark Ransom Jan 31 '18 at 20:42
  • @HolyBlackCat I am trying to develop a game and I was told it was a common downfall to rely on virtual functions due to the potential cache misses that might occur when performing operations over large vectors of entities – Ryoku Jan 31 '18 at 20:42
  • 5
    "I am trying to optimize the run time of my code and I was told that removing unnecessary virtual functions was the way to go." you got a wrong advice. Way to go is to revisit your algorithms and data structures first, then profile and find bottlenecks. Then you would know how to improve. – Slava Jan 31 '18 at 20:43
  • 3
    @Ryoku • if you are getting rid of virtual functions, and using dynamic casting, and groveling for types, and maybe using switch/case blocks you're going down the wrong path. _"I would still like to use inheritance to avoid unnecessary code bloat"_ ... is also not what inheritance is for; inheritance is for code reuse **by the consumer** of the polymorphic object. The answer here is PROFILE PROFILE PROFILE, rather than try to do premature optimization. – Eljay Jan 31 '18 at 20:46
  • 4
    You would get same cache issues with manual dispatching. and your implementation would certainly be worst than virtual (performance and readability)... – Jarod42 Jan 31 '18 at 20:46
  • 2
    What you end up with - poor manual implementation of virtual functions, which will work even worse (if vitual functions would work bad, which is questionable). – Slava Jan 31 '18 at 20:54

2 Answers2

2

Question 1: Is there a better way to store type so that I do not need to waste memory for each instance of the object created (like the static keyword would allow)?

There's the typeid() already enabled with RTTI, there's no need you implement that yourself in an error prone and unreliable way.

Question 2: Would it be more effective to put the switch statement in the function to decide that it should do based on the type value, or switch statement -> downcast then use a derived class specific function.

Certainly no! That's a heavy indicator of bad (sic!) class inheritance hierarchy design.

Question 3: Is there a better way to handle this that I am entirely overlooking that does not use virtual functions? For Example, should I just create an entirely new class that has many of the same variables

The typical way to realize polymorphism without usage of virtual functions is the CRTP (aka Static Polymorphism).
That's a widely used technique to avoid the overhead of virtual function tables when you don't really need them, and just want to adapt your specific needs (e.g. with small targets, where low memory overhead is crucial).

Given your example1, that would be something like this:

template<class Derived>
class A {
protected:
    int InternalGetType() { return 0; }
public:
    int GetType() { static_cast<Derived*>(this)->InternalGetType(); }
};

class B : public A<B> {
    friend class A<B>;
protected:
    int InternalGetType() { return 1; }
};

All binding will be done at compile time, and there's zero runtime overhead.
Also binding is safely guaranteed using the static_cast, that will throw compiler errors, if B doesn't actually inherits A<B>.


Note (almost disclaimer):

Don't use that pattern as a golden hammer! It has it's drawbacks also:

  • It's harder to provide abstract interfaces, and without prior type trait checks or concepts, you'll confuse your clients with hard to read compiler error messages at template instantiantion.

  • That's not applicable for plugin like architecture models, where you really want to have late binding, and modules loaded at runtime.

  • If you don't have really heavy restrictions regarding executable's code size and performance, it's not worth doing the extra work necessary. For most systems you can simply neglect the dispatch overhead done with virtual function defintions.


1)The semantics of GetType() isn't necessarily the best one, but well ...

Community
  • 1
  • 1
1

Go ahead and use virtual functions, but make sure each of those functions is doing enough work that the overhead of an indirect call is insignificant. That shouldn't be very hard to do, a virtual call is pretty fast - it wouldn't be part of C++ if it wasn't.

Doing your own pointer casting is likely to be even slower, unless you can use that pointer a significant number of times.

To make this a little more concrete, here's some code:

class A {
public:
    int type;
    int buffer[1000000];
    A() : type(0) {}
    virtual void VirtualIncrease(int n) { buffer[n] += 1; }
    void NonVirtualIncrease(int n) { buffer[n] += 1; }
    virtual void IncreaseAll() { for i=0; i<1000000; ++i) buffer[i] += 1; }
};

class B : public A {
public:
    B() : {type = 1}
    virtual void VirtualIncrease(int n) { buffer[n] += 2; }
    void NonVirtualIncrease(int n) { buffer[n] += 2; }
    virtual void IncreaseAll() { for i=0; i<1000000; ++i) buffer[i] += 2; }
};

int main() {
    A *a = new B();

    // easy way with virtual
    for (int i = 0; i < 1000000; ++i)
        a->VirtualIncrease(i);

    // hard way with switch
    for (int i = 0; i < 1000000; ++i) {
        switch(a->type) {
            case 0:
                a->NonVirtualIncrease(i);
                break;
            case 1:
                static_cast<B*>(a)->NonVirtualIncrease(i);
                break;
        }
    }

    // fast way
    a->IncreaseAll();

    getchar();
    return 0;
}

The code that switches using a type code is not only much harder to read, it's probably slower as well. Doing more work inside a virtual function ends up being both cleanest and fastest.

Mark Ransom
  • 299,747
  • 42
  • 398
  • 622
  • There are _real life_ use cases, where you don't want to _"go ahead"_ with `virtual` functions. –  Jan 31 '18 at 21:10
  • I already did so, even before writing that comment. Any doubts about the usefulness of _Static Polymorphism_? It has it's use cases, and there's a probability your TV broadcast provider runs some nifty 24/7 up code, based on that design (where I used it). –  Jan 31 '18 at 21:22
  • @TheDude sorry I figured that out shortly after my comment. And it's nothing I didn't know, see my comment on the question. My problem is that there isn't good guidance on when CRTP is appropriate and when it isn't. – Mark Ransom Jan 31 '18 at 21:25
  • _"My problem is that there isn't good guidance on when CRTP is appropriat"_ I mostly used the CRTP, when there's need to have configurable adaptions for specifically targeted hardware, and there's no need to provide any _runtime flexibility_ provided for _plugins_ or such, or providing a framework library, which should be prepared for any runtime circumstances. If you have a hardware, that is fixed in capabilities, you just need to configure and adapt your firmware, no need for runtime extensibility. –  Jan 31 '18 at 21:32
  • I tried to point out more about that in my answer, kindly have a look at it. –  Jan 31 '18 at 21:50