Disclaimer: Compiler Optimization
This answer is about compiler optimization techniques. Your compiler may or may not support these. Even if it supports the technique you are trying to exploit, they may not be available at your chosen optimization level.
Your mileage may vary.
Most Derived Class
The compiler can indeed devirtualize the call if it knows that this is indeed the most derived class. How this could be achieved is discussed here. An example:
struct Base { virtual void call_me_virtual() = nullptr; };
struct Derived final : Base { void call_me_virtual() override { } };
void dosomething(Derived* d) {
d->call_me_virtual();
}
Interestingly, if this is not done, you could always have someone else derive from your class in another translation unit, so the compiler would not be aware of that "more derived" class in your current translation unit.
Another way to ensure that the class must be most derived is to put it into an anonymous namespace:
struct Base { virtual void call_me_virtual() = nullptr; };
namespace {
struct Derived : Base { void call_me_virtual() override { } };
void dosomething(Derived* d) {
d->call_me_virtual();
}
}
This works, because Derived
is not known outside the current translation unit, which implies that any more derived class must be inside the current translation unit. However, this means that dosomething
cannot be (correctly) called from outside the current compilation unit, which is why I have given it internal linkage as well.
One exemption from this rule are compilers that are able to prove that the object originates from within their view, e.g. if Derived
is the most derived type in your current translation unit and the object must always come from within the current translation unit, then it cannot be from any more derived type. The scope of this analysis may be widened by utilizing whole program optimization.
Known Type
The more common case is that of the compiler knowing exactly what type the object is, e.g. in both of these cases:
Derived d;
d.call_me_virtual();
Base* b = new Derived;
b->call_me_virtual();
The compiler may be able to deduce that d.call_me_virtual
and b->call_me_virtual
will both always resolve to Derived::call_me_virtual
and thus devirtualize (and even inline) the call in that instance.
In the first case, this requires knowledge that an object of type Derived
unlike an object of the very similar type Derived&
can only be a Derived
object and not something derived from that.
The second case requires static type analysis, which is being done by modern optimizing compilers. By seeing that b
is always initialized with a pointer to a Derived
object, the compiler is able to prove which function will be called by b->call_me_virtual
.
Finalized Member function
A similar case happens when you finalize a single member function:
struct Base { virtual void call_me_virtual() = nullptr; };
struct Derived : Base { void call_me_virtual() override final { } };
void dosomething(Derived* d) {
d->call_me_virtual();
}
While d
may point to something derived from Derived
, the call_me_virtual
method may not be changed anymore, so the compiler knows that Derived::call_me_virtual
will always be called, and can therefore devirtualize this call.