Are Inline Virtual Functions Really a Non-Sense

Are inline virtual functions really a non-sense?

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."

Can virtual functions be inlined

Whether the compiler chooses to inline a function which is defined inline is entirely up to the compiler. In general, virtual functions can only be inlined when the compiler can either prove that the static type matches the dynamic type or when the compiler can safely determine the dynamic type. For example, when you use a value of type A the compiler knows that the dynamic type cannot be different and it can inline the function. When using a pointer or a reference the compiler generally cannot prove that the static type is the same and virtual functions generally need to follow the usual virtual dispatch. However, even when a pointer is used, the compiler may have enough information from the context to know the exact dynamic type. For example, MatthieuM. gave the following exmaple:

A* a = new B;
a->func();

In this case the compiler can determine that a points to a B object and, thus, call the correct version of func() without dynamic dispatch. Without the need for the dynamic dispatch, func() could then be inlined. Of course, whether compilers do the corresponding analysis depends on its respective implementation.

As hvd correctly pointed out, the virtual dispatch can be circumvented by calling a virtual function will full qualification, e.g., a->A::func(), in which case the virtual function can also be inlined. The main reason virtual functions are generally not inlined is the need to do a virtual dispatch. With the full qualification the function to be called is, however, known.

inline virtual function

Under normal circumstances, a virtual function will be invoked via a pointer to a function (that's contained in the class' vtable). That being the case, a virtual function call can only be generated inline if the compiler can statically determine the actual type for which the function will be invoked, rather than just that it must be class X or something derived from X.

The primary time an inline virtual function makes sense is if you have a performance critical situation, and know that a class will frequently be used in a way that allows the compiler to determine the actual type statically (and at least one target compiler optimizes out the call via pointer).

Why that pure virtual function cannot be inline?

A virtual function 99% of the time cannot be inlined because the function pointer is resolved at runtime using vtable.

The purpose of a virtual function is to be overloaded by subclasses.
An example :

class A
{
virtual int foo() { return 1; }
}

class B : public A
{
virtual int foo() { return 2; }
}

int main(void)
{
B b;
A *a = &b;

return a->foo();
}

The main function will return 2 and not 1.

If the compiler inline the function it will return 1 because the compiler can only assume that the type of *a is A and not B. So the compiler will not do it if he cannot assume safely the type of *a.

In this example the compiler may successfully and safely assume that virtualization is not needed : This is really depending of the compiler and of optimization level.

In some case the compiler can safely assume that virtualization is not needed and only in these cases the inline keyword makes sense.

Even if you declare a function with the keywords inline, the function may not be inlined.

Anyway adding manually the inline keyword is most of the time not a good idea, compiler today are good and automatically inline function when necessary.
By adding inline you may in some case decrease performance, so it a good practice to not abuse of it

C++ virtual function inlining when derived class is final?

The first step is called devirtualization; where a function call does not go through virtual dispatch.

Compilers can and do devirtualize final methods and methods of final classes. That is almost the entire point of final.

Once devirtualized, methods can be inlined.

Some compilers can sometimes prove the static type of *a and even devirtualize that. This is less reliable. Godbolt's compiler explorer can be useful to understand what specific optimizations can happen and how it can fail.

Can a compiler inline a virtual function if I use a pointer in a clear situation?

It doesn't matter whether B has any derived classes. In that situation b points to a B object so the compiler can inline the call.

And surely any decent modern compiler can and will do that in your situation. If you don't use pointers it becomes a whole lot easier. It's not really an "optimization" then. The fact that you can omit a virtual call then becomes obvious by only looking at the AST node at the left side of the .-operator. But if you use pointers, you need to track the dynamic type of the pointee. But modern compilers are capable of that.

EDIT: Some experiment is in order.

// main1.cpp
struct A {
virtual void f();
};

struct B : A {
virtual void f();
};

void g() {
A *a = new A;
a->f();

a = new B;
a->f();
}

// clang -O2 -S -emit-llvm -o - main1.cpp | c++filt
// ...
define void @g()() {
%1 = tail call noalias i8* @operator new(unsigned int)(i32 4)
%2 = bitcast i8* %1 to %struct.A*
%3 = bitcast i8* %1 to i32 (...)***
store i32 (...)** bitcast (i8** getelementptr inbounds ([3 x i8*]* @vtable for A, i32 0, i32 2) to i32 (...)**), i32 (...)*** %3, align 4
tail call void @A::f()(%struct.A* %2)
%4 = tail call noalias i8* @operator new(unsigned int)(i32 4)
%5 = bitcast i8* %4 to i32 (...)***
store i32 (...)** bitcast (i8** getelementptr inbounds ([3 x i8*]* @vtable for B, i32 0, i32 2) to i32 (...)**), i32 (...)*** %5, align 4
%tmp = bitcast i8* %4 to %struct.B*
tail call void @B::f()(%struct.B* %tmp)
ret void
}
// ...

As can be seen, clang does direct calls to f, both when a points to a A and when it points to a B. GCC does that too.

what will happen to inline functions inside virtual functions?

It doesn't matter whether foo is virtual or not. It only matters whether doInlineJob is virtual. It's not, so it can be inlined without a problem.

Inline virtual function when called from another virtual function?

In certain cases, compiler can determine the virtual dispatch behavior in compile-time and perform non-virtual function invocation or even inline the function. It can only do that if it can figure out that your class is the "top" in inheritance chain or those two functions are not otherwise overloaded. Oftentimes, this is simply impossible, especially if you don't have late time optimization enabled for the whole program.

Unless you want to check the results of your compiler's optimizations, your best bet would be not to use a virtual function in the inner loop at all. For example, something like this:

class Foo {
public:
virtual void foo()
{
foo_impl();
}

virtual void bar()
{
for (int i = 0; i < ∞; ++i) {
foo_impl();
}
}

private:
void foo_impl() { /* do some nasty stuff here */ }
};

But in that case you clearly give up the idea that somebody may come in, inherit from your class and throw in their own implementation of "foo" to be called by your "bar". They will essentially will need to re-implement both.

On the other hand, it smells a bit like a premature optimization. Modern CPUs will most likely "lock" your loop, predict the exit from it and execute the same µOPs over and over, even if your method is virtually virtual. So I'd recommend you carefully determine this to be a bottleneck before spending your time optimizing it.



Related Topics



Leave a reply



Submit