class A {
public:
virtual void f() = 0;
};
class B : public A {
public:
void f() final override { };
};
int main() {
B* b = new B();
b->f();
}
In this case, is the compiler required to still do the v-table lookup for b->f();, or can it call B::f() directly because it was marked final?
Is final used for optimization in C++?
It can be, and is.
As noted, it is being used already; see here and here showing the generated code for the override with and without final.
An optimisation along these lines would relate to the "de-virtualization" of the virtual calls. This is not always immediately affected by the final of the class nor method. Albeit they offer help to determine this, the normal rules of the virtual functions and class hierarchy apply.
If the compiler can determine that at runtime a particular method will always be called (e.g. given the OP example, with an automatic object), it could apply such an optimisation anyway, irrespective of whether the method is final or not.
Optimisations fall under the as-if rule, that allow the compiler to apply any transformation so long as the observable behaviour is as-if the original code had been executed.
Related
Consider the following example
#include <iostream>
class Base {
public:
virtual void Interface() = 0;
virtual ~Base() {};
protected:
int Identify() { return SubclassesMustImplement(); }
private:
virtual int SubclassesMustImplement() = 0;
};
class Derived : public Base {
public:
void Interface() override { std::cout << Identify() << std::endl; };
private:
int SubclassesMustImplement() final { return 42; };
};
int main() {
Derived d;
Base* b = &d;
b->Interface();
}
I'm trying to figure out how many virtual table lookups occur here. The first one occurs at b->Interface() which resolves to Derived::Interface. What I am not sure about is if a second virtual table lookup ever occurs. In the body of Derived::Interface, Identify() is not virtual so it is known at compile-time to be Base::Identify(). However, in the body of Base::Interface() we call SubclassesMustImplement(). At this point in the code, is the this pointer referring to a Base or Derived?
If it is pointing to a Base, then a virtual table lookup (ignoring compiler optimizations for a simple case like this) will occur. If it is pointing to a Derived, then final means that no virtual table lookup occurs.
I don't think the standard has too much to say about this, but in practise:
in the body of Base::Interface() we call SubclassesMustImplement(). At this point in the code, is the this pointer referring to a Base or Derived?
Derived. Multiple inheritance excepted, this never changes.
If it is pointing to a Base, then a virtual table lookup (ignoring compiler optimizations for a simple case like this) will occur. If it is pointing to a Derived, then final means that no virtual table lookup occurs.
I don't think you can conclude that (although the compiler might be able to figure it out when the call to Identify can be inlined - you'd have to inspect the generated code at Godbolt to check. If you do this, make sure that optimisations are enabled).
In general, when the compiler generates code for a function body, it neither knows nor cares what type of object this will be pointing to when the function is actually called. Indeed, that is the whole point of using virtual functions. But in this case, your use of pure virtual functions, functions marked final, and function bodies implemented in the class declaration complicates things a bit (and I for one can't predict with any certainty what the compiler will make of all that).
I'm using C++ in an embedded environment where the runtime of virtual functions does matter. I have read about the rare cases when virtual functions can be inlined, for example: Are inline virtual functions really a non-sense?
The accepted answer states that inlining is only possible when the exact class is known at runtime, for example when dealing with a local, global, or static object (not a pointer or reference to the base type). I understand the logic behind this, but I wonder if inlining would be also possible in the following case:
class Base {
public:
inline virtual void x() = 0;
}
class Derived final : Base {
public:
inline virtual void x(){
cout << "inlined?";
}
}
int main(){
Base* a;
Derived* b;
b = new Derived();
a = b;
a->x(); //This can definitely not be inlined.
b->x(); //Can this be inlined?
}
From my point of view the compiler should know the definitive type of a at compiletime, as it is a final class. Is it possible to inline the virtual function in this case? If not, then why? If yes, then does the gcc-compiler (respectively avr-gcc) does so?
Thanks!
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.
C++11 added final.
Finally!
I understand final does two things:
Makes a class non-inheritable.
Makes (virtual) functions in a class non-overridable (in a derived class).
Both of these seem independent of each other. But take for example the following:
class Foo
{
public:
virtual void bar()
{
//do something unimportant.
}
};
class Baz final : public Foo
{
public:
void bar() /*final*/ override
{
//do something more important than Foo's bar.
}
};
From above, I believe Baz being final, I should NOT need to specify that its virtual member function bar is also final. Since Baz cannot be inherited, the question of overriding bar goes out of scope. However my compiler VC++ 2015, is very quiet about this. I have not tested this on any others at the moment.
I would be glad if someone could shed some light on this topic. A quote from the standard (if any) would be extremely appreciated. Also please state any corner cases that I am unaware of, that may cause my logical belief to fail.
So, my question is: Does a final class implicitly imply its virtual functions to be final as well? Should it? Please clarify.
The reason I am asking this is because final functions become qualified for de-virtualization, which is a great optimization. Any help is appreciated.
The reason I am asking this is because final functions become qualified for de-virtualization, which is a great optimization.
Do they? "De-virtualization" is not part of the C++ standard. Or at least, not really.
De-virtualization is merely a consequence of the "as if" rule, which states that the implementation can do whatever it likes so long as the implementation behaves "as if" it is doing what the standard says.
If the compiler can detect at compile-time that a particular call to a virtual member function, through a polymorphic type, will undeniably call a specific version of that function, then it is allowed to avoid using the virtual dispatching logic and calling the function statically. That's behaving "as if" it had used the virtual dispatching logic, since the compiler can prove that this is the function that would have been called.
As such, the standard does not define when de-virtualization is allowed/forbidden. A compiler, upon inlining a function that takes a pointer to a base class type, may find that the pointer being passed is pointing to a stack variable local declared in the function that it is being inlined within. Or that the compiler can trace down a particular inline/call graph to the point of origin for a particular polymorphic pointer/reference. In those cases, the compiler can de-virtualize calls into that type. But only if it's smart enough to do so.
Will a compiler devirtualize all virtual function calls to a final class, regardless of whether those methods are declared final themselves? It may. It may not. It may not even devirtualize any calls to methods declared final on the polymorphic type. That's a valid (if not particularly bright) implementation.
The question you're asking is implementation specific. It can vary from compiler to compiler.
However, a class being declared final, as you pointed out, ought to be sufficient information for the compiler to devirtualize all calls to pointers/references to the final class type. If a compiler doesn't do so, then that's a quality-of-implementation issue, not a standards one.
To quote the draft C++ standard from here [class.virtual/4]:
If a virtual function f in some class B is marked with the virt-specifier final and in a class D derived from B a function D::f overrides B::f, the program is ill-formed.
And here [class/3]:
If a class is marked with the class-virt-specifier final and it appears as a base-type-specifier in a base-clause (Clause [class.derived]), the program is ill-formed.
So, in answer to the question;
Does a final class implicitly imply its virtual functions to be final as well? Should it? Please clarify.
So, at least not formally. Any attempt to violate either rule will have the same result in both cases; the program is ill-formed and won't compile. A final class means the class cannot be derived from, so as a consequence of this, its virtual methods cannot be overridden.
Should it? At least formally, probably not; they are related but they are not the same thing. There is also no need formally require the one to imply the other, the effect follows naturally. Any violations have the same result, a failed compilation (hopefully with appropriate error messages to distinguish the two).
To touch on your motivation for the query and the de-virtualization of the virtual calls. This is not always immediately affected by the final of the class nor method (albeit they offer help), the normal rules of the virtual functions and class hierarchy apply.
If the compiler can determine that at runtime a particular method will always be called (e.g. with an automatic object, i.e. "on the stack"), it could apply such an optimisation anyway, irrespective of the method being marked final or not. These optimisations fall under the "as-if" rule, that allow the compiler to apply any transformation so long as the observable behaviour is as-if the original code had been executed.
Does a final class implicitly imply its virtual functions to be final as well?
[...]
I am asking this is because final functions become qualified for de-virtualization, which is a great optimization.
Yes, it does, for the purposes of de-virtualization, in all major compilers (including MSVC):
struct B { virtual void f() = 0; };
struct D1 : public B { void f(); };
struct D2 : public B { void f() final; };
struct D3 final : public B { void f(); };
void f1(D1& x) { x.f(); } // Not de-virtualized
void f2(D2& x) { x.f(); } // De-virtualized
void f3(D3& x) { x.f(); } // De-virtualized
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?
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.)
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.
De-virtualization is, actually, a very special case of constant propagation, where the constant propagated is the type (physically represented as a v-ptr in general, but the Standard makes not such guarantee).
Total devirtualization
There are multiple situations where a compiler can actually devirtualize a call that you may not think about:
int main() {
Base* base = new Derived();
base->foo();
}
Clang is able to devirtualize the call in the above example simply because it can track the actual type of base as it is created in scope.
In a similar vein:
struct Base { virtual void foo() = 0; };
struct Derived: Base { virtual void foo() override {} };
Base* create() { return new Derived(); }
int main() {
Base* base = create();
base->foo();
}
while this example is slightly more complicated, and the Clang front-end will not realize that base is necessarily of type Derived, the LLVM optimizer which comes afterward will:
inline create in main
store a pointer to the v-table of Derived in base->vptr
realize that base->foo() therefore is base->Derived::foo() (by resolving the indirection through the v-ptr)
and finally optimize everything out because there is nothing to do in Derived::foo
And here is the final result (which I assume needs no comment even for those not initiated to the LLVM IR):
define i32 #main() #0 {
ret i32 0
}
There are multiple instances where a compiler (either front-end or back-end) can devirtualize calls in situations that might not be obvious, in all cases it boils down to its ability to prove the run-time type of the object pointed to.
Partial devirtualization
In his serie about improvements to the gcc compiler on the subject of devirutalization Jan Hubička introduces partial devirtualization.
The latest incarnations of gcc have the ability to short-list a few likely run-time types of the object, and especially produce the following pseudo-code (in this case, two are deemed likely, and not all are known or likely enough to justify a special case):
// Source
void doit(Base* base) { base->foo(); }
// Optimized
void doit(Base* base) {
if (base->vptr == &Derived::VTable) { base->Derived::foo(); }
else if (base->ptr == &Other::VTable) { base->Other::foo(); }
else {
(*base->vptr[Base::VTable::FooIndex])(base);
}
}
While this may seem slightly convoluted, it does offer some performance gains (as you'll see from the serie of articles) in case the predictions are correct.
Seems surprising? Well, there are more tests, but base->Derived::foo() and base->Other::foo() can now be inlined, which itself opens up further optimization opportunities:
in this particular case, since Derived::foo() does nothing, the function call can be optimized away; the penalty of the if test is less than that of a function call so it's worth it if the condition matches often enough
in cases where one of the function arguments is known, or known to have some specific properties, the subsequent constant propagation passes can simplify the inlined body of the function
Impressive, right?
Alright, alright, this is rather long-winded but I am coming to talk about dynamic_cast<Derived*>(base)!
First of all, the cost of a dynamic_cast is not to be underestimated; it might well, actually, be more costly than calling base->foo() in the first place, you've been warned.
Secondly, using dynamic_cast<Derived*>(base)->foo() can, indeed, allow devirtualizing the function call if it gives sufficient information to the compiler to do so (it always gives more information, at least). Typically, this can be either:
because Derived::foo is final
because Derived is final
because Derived is defined in an anonymous namespace and has no descendant redefining foo, and thus only accessible in this translation unit (roughly, .cpp file) and so all its descendants are known and can be checked
and plenty of other cases (like pruning the set of potential candidates in the case of partial devirtualization)
If you really wish to ensure devirtualization, though, final applied either on the function or class is your best bet.
I've seen it stated that C++ has name hiding for the purposes of reducing the fragile base class problem. However, I definitely don't see how this helps. If the base class introduces a function or overload that previously did not exist, it might conflict with those introduced by the derived class, or unqualified calls to global functions or member functions- but what I don't see is how this is different for overloads. Why should overloads of virtual functions be treated differently to, well, any other function?
Edit: Let me show you a little more what I'm talking about.
struct base {
virtual void foo();
virtual void foo(int);
virtual void bar();
virtual ~base();
};
struct derived : base {
virtual void foo();
};
int main() {
derived d;
d.foo(1); // Error- foo(int) is hidden
d.bar(); // Fine- calls base::bar()
}
Here, foo(int) is treated differently to bar(), because it's an overload.
I'll assume that by "fragile base class", you mean a situation where changes to the base class can break code that uses derived classes (that being the definition I found on Wikipedia). I'm not sure what virtual functions have to do with this, but I can explain how hiding helps avoid this problem. Consider the following:
struct A {};
struct B : public A
{
void f(float);
};
void do_stuff()
{
B b;
b.f(3);
}
The function call in do_stuff calls B::f(float).
Now suppose someone modifies the base class, and adds a function void f(int);. Without hiding, this would be a better match for the function argument in main; you've either changed the behaviour of do_stuff (if the new function is public), or caused a compile error (if it's private), without changing either do_stuff or any of its direct dependencies. With hiding, you haven't changed the behaviour, and such breakage is only possible if you explicitly disable hiding with a using declaration.
I don't think that overloads of virtual functions are treated any differently that overloads of regular functions. There might be one side effect though.
Suppose we have a 3 layers hierarchy:
struct Base {};
struct Derived: Base { void foo(int i); };
struct Top: Derived { void foo(int i); }; // hides Derived::foo
When I write:
void bar(Derived& d) { d.foo(3); }
the call is statically resolved to Derived::foo, whatever the true (runtime) type that d may have.
However, if I then introduce virtual void foo(int i); in Base, then everything changes. Suddenly Derived::foo and Top::foo become overrides, instead of mere overload that hid the name in their respective base class.
This means that d.foo(3); is now resolved statically not directly to a method call, but to a virtual dispatch.
Therefore Top top; bar(top) will call Top::foo (via virtual dispatch), where it previously called Derived::foo.
It might not be desirable. It could be fixed by explicitly qualifying the call d.Derived::foo(3);, but it sure is an unfortunate side effect.
Of course, it is primarily a design problem. It will only happen if the signature are compatible, else we'll have name hiding, and no override; therefore one could argue that having "potential" overrides for non-virtual functions is inviting troubles anyway (dunno if any warning exist for this, it could warrant one, to prevent being put in such a situation).
Note: if we remove Top, then it is perfectly fine to introduce the new virtual method, since all old calls were already handled by Derived::foo anyway, and thus only new code may be impacted
It is something to keep in mind though when introducing new virtual methods in a base class, especially when the impacted code is unknown (libraries delivered to clients).
Note that C++0x has the override attribute to check that a method is truly an override of a base virtual; while it does not solve the immediate problem, in the future we might imagine compilers having a warning for "accidental" overrides (ie, overrides not marked as such) in which case such an issue could be caught at compile-time after the introduction of the virtual method.
In The Design and Evolution of C++, Bjarne Stroustrup Addison-Weslay, 1994 section 3.5.3 pp 77, 78, B.S. explains that the rule by which a name in a derived class hides all definition of the same name in its base classes is old and dates back from C with Classes. When it was introduced, B.S. considered it as the obvious consequence of scoping rules (it's the same for nested blocks of code or nested namespaces — even if namespace were introduced after). The desirability of its interactions with overloading rules (the overloaded set doesn't contain the function defined in the base classes, nor in the enclosing blocks — now harmless as declaring functions in block is old fashioned —, nor in enclosing namespaces where the problem occasionally strikes as well) has been debated, to the point that G++ implemented alternative rules allowing the overloading, and B.S. argued that the current rule helps preventing errors in situations like (inspired from real live problems with g++)
class X {
int x;
public:
virtual void copy(X* p) { x = p->x; }
};
class XX: public X {
int xx;
public:
virtual void copy(XX* p) { xx = p->xx; X::copy(p); }
};
void f(X a, XX b)
{
a.copy(&b); // ok: copy X part of b
b.copy(&a); // error: copy(X*) is hidden by copy(XX*)
}
Then B.S. continues
In retrospect, I suspect that the overloading rules introduced in 2.0 might have been able to handle this case. Consider the call b.copy(&a). The variable b is an exact type match for the implicit argument of XX::copy, but requires a standard conversion to match X::copy. The variable a on the other hand, is an exact match for the explicit argument of X::copy, but requires a standard conversion to match XX:copy. Thus, had the overloading been allowed, the call would have been an error because it is ambiguous.
But I fail to see where the ambiguity is. It seems to me that B.S. overlooked the fact that &a can't be implicitly converted to a XX* and thus only X::copy has be considered.
Indeed trying with free (friends) functions
void copy(X* t, X* p) { t->x = p->x; }
void copy(XX* t, XX* p) { t-xx = p->xx; copy((X*)t, (X*)p); }
I get no ambiguity error with current compilers and I don't see how rules in the Annotated C++ Reference Manual would makes a difference here.