Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Are there any costs to using a virtual function if objects are cast to their actual type?

My understanding is that virtual functions can cause performance problems because of two issues: the extra derefencing caused by the vtable and the inability of compilers to inline functions in polymorphic code.

What if I downcast a variable pointer to its exact type? Are there still any extra costs then?

class Base { virtual void foo() = 0; };
class Derived : public Base { void foo() { /* code */} };

int main() {
    Base * pbase = new Derived();
    pbase->foo(); // Can't inline this and have to go through vtable
    Derived * pderived = dynamic_cast<Derived *>(pbase);
    pderived->foo(); // Are there any costs due to the virtual method here?
}

My intuition tells me that since I cast the object to its actual type, the compiler should be able to avoid the disadvantages of using a virtual function (e.g., it should be able to inline the method call if it wants to). Is this correct?

Can the compiler actually know that pderived is of type Derived after I downcast it? In the example above its trivial to see that pbase is of type Derived but in actual code it might be unknown at compile time.

Now that I've written this down, I suppose that since the Derived class could itself be inherited by another class, downcasting pbase to a Derived pointer does not actually ensure anything to the compiler and thus it is not able to avoid the costs of having a virtual function?

like image 318
Kevin Salvesen Avatar asked Jul 10 '15 10:07

Kevin Salvesen


People also ask

What happens if we don't use the virtual function in the inheritance?

If you don't use virtual functions, you don't understand OOP yet. Because the virtual function is intimately bound with the concept of type, and type is at the core of object-oriented programming, there is no analog to the virtual function in a traditional procedural language.

What are the rules to be considered for virtual function?

Rules for Virtual FunctionsVirtual functions cannot be static. A virtual function can be a friend function of another class. Virtual functions should be accessed using pointer or reference of base class type to achieve runtime polymorphism.

Do virtual functions have to be overridden?

¶ Δ A pure virtual function is a function that must be overridden in a derived class and need not be defined. A virtual function is declared to be “pure” using the curious =0 syntax.

What are the implications of making a function a pure virtual function?

A pure virtual function makes it so the base class can not be instantiated, and the derived classes are forced to define these functions before they can be instantiated. This helps ensure the derived classes do not forget to redefine functions that the base class was expecting them to.


2 Answers

There's always a gap between what the mythical Sufficiently Smart Compiler can do, and what actual compilers end up doing. In your example, since there is nothing inheriting from Derived, the latest compilers will likely devirtualize the call to foo. However, since successful devirtualization and subsequent inlining is a difficult problem in general, help the compiler out whenever possible by using the final keyword.

class Derived : public Base { void foo() final { /* code */} }

Now, the compiler knows that there's only one possible foo that a Derived* can call.

(For an in-depth discussion of why devirtualization is hard and how gcc4.9+ tackles it, read Jan Hubicka's Devirtualization in C++ series posts.)

like image 63
Pradhan Avatar answered Sep 25 '22 20:09

Pradhan


Pradhan's advice to use final is sound, if changing the Derived class is an option for you and you don't want any further derivation.

Another option directly available to specific call sites is prefixing the function name with Derived::, inhibiting virtual dispatch to any further override:

#include <iostream>

struct Base { virtual ~Base() { } virtual void foo() = 0; };

struct Derived : public Base
{
    void foo() override { std::cout << "Derived\n"; }
};

struct FurtherDerived : public Derived
{
    void foo() override { std::cout << "FurtherDerived\n"; }
};

int main()
{
    Base* pbase = new FurtherDerived();
    pbase->foo(); // Can't inline this and have to go through vtable
    if (Derived* pderived = dynamic_cast<Derived *>(pbase))
    {
        pderived->foo();  // still dispatched to FurtherDerived
        pderived->Derived::foo();  // static dispatch to Derived
    }
}

Output:

FurtherDerived
FurtherDerived
Derived

This can be dangerous: the actual runtime type might depend on its overrides being called to maintain its invariants, so it's a bad idea to use it unless there're pressing performance problems.

Code available here.

like image 31
Tony Delroy Avatar answered Sep 25 '22 20:09

Tony Delroy