0

For a project of mine, I finally need to work with my first polymorphic class (except std::cout).

I am researching how to be sure I have 100% devirtualized calls at least in some cases.

Is this code legal and viable?

How slow will be the dynamic_cast? If I pay it once in the constructor, then I will be able always to use class B directly, so it sounds as good practice?

Will C style cast be fast after that or I must store pointer to B?

struct A{
    virtual void x();
};

struct B : A{
    void x() override;
};

struct X{
    A *a;
    bool fB;

    X(A &a) : a(&a), fB( dynamic_cast<B *>(&a) ){}

    void f(){
        if (fB){
            ((B*)a)->x();
        }else{
            a->x();
        }
    }
};


void fn(A &a){
    X x(a);

    x.f();
}


int main(){
    B b;

    X x(b);

    x.f();
}
Nick
  • 9,962
  • 4
  • 42
  • 80
  • 2
    The nice thing about plymorphism is that virtual functions will be resolved and called on the correct class automatically. So you don't need `fB` or the dynamic cast. Just `void f() { a->x(); }` should work fine. – Some programmer dude Nov 06 '19 at 09:38
  • 3
    And as a general tip: All C-style casting should be seen as a red flag that you do something wrong. – Some programmer dude Nov 06 '19 at 09:39
  • it seems you missing the point. calling non devirtualized virtual function in a loop, results in resolving virtual function every single time. – Nick Nov 06 '19 at 09:53
  • Then you need to explain *why* you need to "devirtualize" calls. What is the actual and underlying problem you need to solve. – Some programmer dude Nov 06 '19 at 09:57
  • 1
    because I want performance, but I do not wish to change the type of `X`, else I would use template – Nick Nov 06 '19 at 09:58
  • 2
    Then the natural follow-up question is if you have measured this to be a top one or two bottleneck in your program? All manual and hand-made optimizations (including compile-time polymorphism or other attempts to go around virtual dispatch) will lead to obscure and obtuse code that needs plenty of documentation to explain not only what's being done but also why. It also tend to lead to obscure bugs and need a lot of extra testing. And if done poorly will not result in any (real or perceived) change at all in "efficiency". – Some programmer dude Nov 06 '19 at 10:03

2 Answers2

1

You try to change a virtual call to a branch.

I already not sure it was faster,

but worse, you don't remove the virtual call as B can have subchild, you at least have to make void B::x() final to allow compiler to devirtualize the call.

If compiler has access to concrete type (and to the code) as in main, it might be able to devirtualize alone the call.

Jarod42
  • 203,559
  • 14
  • 181
  • 302
1

With your existing code, I actually would hope your compiler optimizes away the if-statement and your cast as this is pesimizing the code.

From the compiler's point of view, your code-base could be looking the following way:

struct A{
    virtual void x();
};

struct B : A{
    void x() override;
};

struct C : B{
    void x() override;
};

So, when is sees your cast and function call: static_cast<B*>(a)->x() it still has to access the same virtual table as to when calling a->x() as there could be a potential class C. (Please note that I use static_cast, as c-style casts are sources for bugs)

If you want to get a direct call to B, you better make the method or class final. Another good approach is using profile guided optimization, in which case, they often compare the vtable pointers.

To answer your side questions?

  • Is the code legal? Yes, it is.
  • Is this viable? Yes, given the remarks above.
  • How slow will the dynamic_cast? Slow, I would argue to write a good benchmark for this, however, I wouldn't know how to do so and make it realistic.
  • Is this good practice? No, it makes the polymorphism less usable.
  • Will the static_cast be fast? Yes, it's fast, in this specific case, there ain't any instructions needed. Storing a pointer to B could both improve in specific cases of complex inheritance compared to the bool. If it would take extra memory, it could cause a decrease in performance as well. Another decrease could come well by just having extra assembly in your executable.

My advice, especially since you are new to C++: Don't do this or any other manual optimizations. Compilers can do a lot for you. Every manual optimization causes extra maintenance afterward, only use these kinds of tricks if you actually have a performance problem.

Oh, and if you want to know what the actual assembly code it, you can experiment at compiler explorer

JVApen
  • 11,008
  • 5
  • 31
  • 67