Are inline virtual functions really a non-sense?

90,381

Solution 1

Virtual functions can be inlined sometimes. An excerpt from the excellent C++ faq:

"The only time an inline virtual call can be inlined is when the compiler knows the "exact class" of the object which is the target of the virtual function call. This can happen only when the compiler has an actual object rather than a pointer or reference to an object. I.e., either with a local object, a global/static object, or a fully contained object inside a composite."

Solution 2

C++11 has added final. This changes the accepted answer: it's no longer necessary to know the exact class of the object, it's sufficient to know the object has at least the class type in which the function was declared final:

class A { 
  virtual void foo();
};
class B : public A {
  inline virtual void foo() final { } 
};
class C : public B
{
};

void bar(B const& b) {
  A const& a = b; // Allowed, every B is an A.
  a.foo(); // Call to B::foo() can be inlined, even if b is actually a class C.
}

Solution 3

There is one category of virtual functions where it still makes sense to have them inline. Consider the following case:

class Base {
public:
  inline virtual ~Base () { }
};

class Derived1 : public Base {
  inline virtual ~Derived1 () { } // Implicitly calls Base::~Base ();
};

class Derived2 : public Derived1 {
  inline virtual ~Derived2 () { } // Implicitly calls Derived1::~Derived1 ();
};

void foo (Base * base) {
  delete base;             // Virtual call
}

The call to delete 'base', will perform a virtual call to call correct derived class destructor, this call is not inlined. However because each destructor calls it's parent destructor (which in these cases are empty), the compiler can inline those calls, since they do not call the base class functions virtually.

The same principle exists for base class constructors or for any set of functions where the derived implementation also calls the base classes implementation.

Solution 4

I've seen compilers that don't emit any v-table if no non-inline function at all exists (and defined in one implementation file instead of a header then). They would throw errors like missing vtable-for-class-A or something similar, and you would be confused as hell, as i was.

Indeed, that's not conformant with the Standard, but it happens so consider putting at least one virtual function not in the header (if only the virtual destructor), so that the compiler could emit a vtable for the class at that place. I know it happens with some versions of gcc.

As someone mentioned, inline virtual functions can be a benefit sometimes, but of course most often you will use it when you do not know the dynamic type of the object, because that was the whole reason for virtual in the first place.

The compiler however can't completely ignore inline. It has other semantics apart from speeding up a function-call. The implicit inline for in-class definitions is the mechanism which allows you to put the definition into the header: Only inline functions can be defined multiple times throughout the whole program without a violation any rules. In the end, it behaves as you would have defined it only once in the whole program, even though you included the header multiple times into different files linked together.

Solution 5

Well, actually virtual functions can always be inlined, as long they're statically linked together: suppose we have an abstract class Base with a virtual function F and derived classes Derived1 and Derived2:

class Base {
  virtual void F() = 0;
};

class Derived1 : public Base {
  virtual void F();
};

class Derived2 : public Base {
  virtual void F();
};

An hypotetical call b->F(); (with b of type Base*) is obviously virtual. But you (or the compiler...) could rewrite it like so (suppose typeof is a typeid-like function that returns a value that can be used in a switch)

switch (typeof(b)) {
  case Derived1: b->Derived1::F(); break; // static, inlineable call
  case Derived2: b->Derived2::F(); break; // static, inlineable call
  case Base:     assert(!"pure virtual function call!");
  default:       b->F(); break; // virtual call (dyn-loaded code)
}

while we still need RTTI for the typeof, the call can effectively be inlined by, basically, embedding the vtable inside the instruction stream and specializing the call for all the involved classes. This could be also generalized by specializing only a few classes (say, just Derived1):

switch (typeof(b)) {
  case Derived1: b->Derived1::F(); break; // hot path
  default:       b->F(); break; // default virtual call, cold path
}
Share:
90,381

Related videos on Youtube

aJ.
Author by

aJ.

I am a C++ developer from INDIA, interested in C, C#.NET, Perl.

Updated on June 30, 2020

Comments

  • aJ.
    aJ. about 4 years

    I got this question when I received a code review comment saying virtual functions need not be inline.

    I thought inline virtual functions could come in handy in scenarios where functions are called on objects directly. But the counter-argument came to my mind is -- why would one want to define virtual and then use objects to call methods?

    Is it best not to use inline virtual functions, since they're almost never expanded anyway?

    Code snippet I used for analysis:

    class Temp
    {
    public:
    
        virtual ~Temp()
        {
        }
        virtual void myVirtualFunction() const
        {
            cout<<"Temp::myVirtualFunction"<<endl;
        }
    
    };
    
    class TempDerived : public Temp
    {
    public:
    
        void myVirtualFunction() const
        {
            cout<<"TempDerived::myVirtualFunction"<<endl;
        }
    
    };
    
    int main(void) 
    {
        TempDerived aDerivedObj;
        //Compiler thinks it's safe to expand the virtual functions
        aDerivedObj.myVirtualFunction();
    
        //type of object Temp points to is always known;
        //does compiler still expand virtual functions?
        //I doubt compiler would be this much intelligent!
        Temp* pTemp = &aDerivedObj;
        pTemp->myVirtualFunction();
    
        return 0;
    }
    
    • Thomas L Holaday
      Thomas L Holaday about 15 years
      Consider compiling an example with whatever switches you need to get an assembler listing, and then showing the code reviewer that, indeed, the compiler can inline virtual functions.
    • mip
      mip about 14 years
      The above usually will not be inlined, because you are calling virtual function in aid of base class. Although it depends only on how smart the compiler is. If it would be able to point out that pTemp->myVirtualFunction() could be resolved as non-virtual call, it might have inline that call. This referenced call is inlined by g++ 3.4.2: TempDerived & pTemp = aDerivedObj; pTemp.myVirtualFunction(); Your code is not.
    • Simon Richter
      Simon Richter over 6 years
      One thing gcc actually does is compare the vtable entry to a specific symbol and then use an inlined variant in a loop if it matches. This is especially useful if the inlined function is empty and the loop can be eliminated in this case.
    • curiousguy
      curiousguy about 6 years
      @doc Modern compiler try hard to determine at compile time the possible values of pointers. Just using a pointer isn't sufficient to prevent inlining at any significant optimization level; GCC even performs simplifications at optimization zero!
  • sharptooth
    sharptooth about 15 years
    When you call a base class method from the same or derived class the call is unambiguous and non-virtual
  • David Rodríguez - dribeas
    David Rodríguez - dribeas about 15 years
    @sharptooth: but then it would be a non-virtual inline method. The compiler can inline functions you don't ask it to, and it probably knows better when to inline or not. Let it decide.
  • sharptooth
    sharptooth about 15 years
    @dribeas: Yes, that's exactly what I'm talking about. I only objected to the statement that virtual finctions are resolved at runtime - this is true only when the call is done virtually, not for the exact class.
  • sharptooth
    sharptooth about 15 years
    True, but it's worth remembering that the compiler is free to ignore the inline specifier even if the call can be resolved at compile time and can be inlined.
  • RnR
    RnR about 15 years
    Amother situation when I think inlining can happen is when you'd call the method for example as this->Temp::myVirtualFunction() - such invokation skips the virtual table resolution and the function should be inlined without problems - why and if you'd want to do it is another topic :)
  • Thomas L Holaday
    Thomas L Holaday about 15 years
    Which version of g++ won't compile inline virtuals?
  • Richard Corden
    Richard Corden about 15 years
    @RnR. It's not necessary to have 'this->', just using the qualified name is enough. And this behaviour takes place for destructors, constructors and in general for assignment operators (see my answer).
  • Richard Corden
    Richard Corden about 15 years
    For compilers that operate on single TUs only, they can only inline implicitly functions that they have the definition for. A function can only be defined in multiple TUs if you make it inline. 'inline' is more than a hint and it can have a dramatic performance improvement for a g++/makefile build.
  • moonshadow
    moonshadow about 15 years
    Hm. The 4.1.1 I have here now appears to be happy. I first encountered problems with this codebase using a 4.0.x. Guess my info is out of date, edited.
  • Colen
    Colen about 15 years
    sharptooth - true, but AFAIK this is true of all inline functions, not just virtual inline functions.
  • ed9w2in6
    ed9w2in6 almost 14 years
    void f(const Base& lhs, const Base& rhs) { } ------In implementation of the function, you never know what lhs and rhs points to until runtime.
  • Philip
    Philip over 12 years
    One should be aware though that empty braces don't always mean the destructor does nothing. Destructors default-destruct every member object in the class, so if you have a few vectors in the base class that could be quite a lot of work in those empty braces!
  • Admin
    Admin over 10 years
    lzprmgr - that method/function can certainly be inlined, no matter what lhs and rhs is.
  • Admin
    Admin over 10 years
    I believe that's nonsense. Any function can always be inlined, no matter how big it is or whether it's virtual or not. It depends on how the compiler was written. If you do not agree, then I expect that your compiler cannot produce non-inlined code either. That is: The compiler can include code that at runtime tests for the conditions it could not resolve at compile-time. It's just like the modern compilers can resolve constant values/reduce nummeric expressions at compile-time. If a function/method is not inlined, it does not mean it cannot be inlined.
  • BeeOnRope
    BeeOnRope about 8 years
    @BaiyanHuang - well, often you do know at compile time, when the method f() itself gets inlined, which may allow the compiler to deduce the types of lhs and rhs. Furthermore, even though lhs or rhs may have arbitrary types, some of the methods could be declared final which would allow inlining. Finally, there are other cases where methods can be proven not overridden, such as classes declared in anonymous namespaces.
  • Yola
    Yola almost 7 years
    Wasn't able to inline it in VS 2017.
  • Jeffrey Faust
    Jeffrey Faust over 6 years
    I don't think it works this way. Invocation of foo() through a pointer/reference of type A can never be inlined. Calling b.foo() should allow inlining. Unless you are suggesting that the compiler already knows this is a type B because it's aware of the previous line. But that's not the typical usage.
  • Jeffrey Faust
    Jeffrey Faust over 6 years
    For example, compare the generated code for bar and bas here: godbolt.org/g/xy3rNh
  • Alexey Romanov
    Alexey Romanov about 6 years
    @JeffreyFaust There's no reason that information shouldn't be propagated, is there? And icc seems to do it, according to that link.
  • Jeffrey Faust
    Jeffrey Faust about 6 years
    @AlexeyRomanov Compilers have freedom to optimize beyond the standard, and the certainly do! For simple cases like above, the compiler could know the type and do this optimization. Things are rarely this simple, and it's not typical to be able to determine the actual type of a polymorphic variable at compile time. I think OP cares about 'in general' and not for these special cases.
  • Alex Meiburg
    Alex Meiburg almost 5 years
    Are they any compilers that do this? Or is this just speculation? Sorry if I'm overly skeptical, but your tone in the description above sounds sort of like -- "they totally could do this!", which is different from "some compilers do this".
  • CAFxX
    CAFxX almost 5 years
    Yes, Graal does polymorphic inlining (also for LLVM bitcode via Sulong)
  • Bas
    Bas about 2 years
    You don't need inline when you define the function in the class. It's implicit.