If I define a class like this:
class A{
public:
A(){}
virtual ~A(){}
virtual void func(){}
};
Does it mean that that the virtual destructor and func
are inlined
If I define a class like this:
class A{
public:
A(){}
virtual ~A(){}
virtual void func(){}
};
Does it mean that that the virtual destructor and func
are 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 example:
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.
a->A::func()
) is another somewhat obvious example where inlining generally works. –
Putrescent Base* b = new Derived{}; b->func();
, here the call can be inlined if the compiler is smart enough to realized that the dynamic type of b
is necessarily Derived
. Clang is such a smart compiler. –
Colet Yes, and in multiple ways. You can see some examples of devirtualization in this email I sent to the Clang mailing list about 2 years ago.
Like all optimizations, this is pending the compiler abilities to eliminate alternatives: if it can prove that the virtual call is always resolved in Derived::func
then it can call it directly.
There are various situations, let us start first with the semantic evidences:
SomeDerived& d
where SomeDerived
is final
allows to devirtualization of all method callsSomeDerived& d
, d.foo()
where foo
is final
also allows devirtualization of this particular callThen, there are situations where you know the dynamic type of the object:
SomeDerived d;
=> the dynamic type of d
is necessarily SomeDerived
SomeDerived d; Base& b;
=> the dynamic type of b
is necessarily SomeDerived
Those 4 devirtualization situations are usually solved by the compiler front-end because they require fundamental knowledge about the language semantics. I can attest that all 4 are implemented in Clang, and I would think they are also implemented in gcc.
However, there are plenty of situations where this breaks down:
struct Base { virtual void foo() = 0; };
struct Derived: Base { virtual void foo() { std::cout << "Hello, World!\n"; };
void opaque(Base& b);
void print(Base& b) { b.foo(); }
int main() {
Derived d;
opaque(d);
print(d);
}
Even though here it is obvious that the call to foo
is resolved to Derived::foo
, Clang/LLVM will not optimize it. The issue is that:
print(d)
by d.foo()
and devirtualize the callprint(d)
by d.foo()
it assumes that the virtual pointer of d
could have been changed by opaque
(whose definition is opaque, as the name implies)I've followed efforts on the Clang and LLVM mailing list as both sets of developers reasoned about the loss of information and how to get Clang to tell LLVM: "it's okay" but unfortunately the issue is non-trivial and has not been solved yet... thus the half-assed devirtualization in the front-end to try and get all obvious cases, and some not so obvious ones (even though, by convention, the front-end is not where you implement them).
For reference, the code for the devirtualization in Clang can be found in CGExprCXX.cpp in a function called canDevirtualizeMemberFunctionCalls
. It's only ~64 lines long (right now) and thoroughly commented.
© 2022 - 2024 — McMap. All rights reserved.