There has been recent discussion about a keyword "final" that has the general effect of allowing the compiler some benefits of closed-system analysis on an open system by enabling user code to place restrictions on ways classes can be extended. More concretely, it can disable the virtual call mechanism and allow static type constraining and optimization.
Please look over my description of a possible "override
" construct that has some relevancy here.
The final
construct can be applied at different granularities in "library" code, and used with references to objects in "user" code.
In library code, final
can be applied to an entire class, thus preventing derivation. It can also be applied to an individual virtual
member of a class, preventing further overriding of it in a derived class. In a related way, a base class can be declared final
so that all inherited virtual
members are treated as final
in the derived class.
In user code, the object type in a reference can be declared final
to indicate that the object referred to is of that exact type (and not a derived type), thus defeating polymorphism.
While these may seem useless at first, one comes to see that in some situations, this restriction is useful to program correctness and efficiency.
In the code examples below, the syntactic placement of the final
keyword is open to change - it is just meant to convey the semantic meaning. Also, when "reference" is used, the meaning is both a pointer or a reference; the same issues apply to both.
A final
class cannot be derived from.
class Base {
public:
virtual void f();
};
final class NoChildren : public Base {
public:
virtual void f();
};
void f( NoChildren& nc ) {
NoChildren final& fnc = nc; // OK
}
In this example, NoChildren
cannot have any derived classes. This allows it to make assumptions in its member functions, and allows the compiler to optimize any virtual calls through references to objects of that type.
In f()
, the final
reference initialization is OK, because any references to a Derived
cannot refer to any class derived from that.
This illustrates the most basic idea of final
- to allow optimizations usually restricted to that can be done in a closed system. In a closed system, the compiler could determine if NoChildren
did in fact have any derived classes or not, and perform appropriate optimizations if it didn't. However, in an open system, it cannot (for example, a system where code can be loaded in dynamically, as dynamic-link libraries commonly allow). A final
class allows this optimization is such systems in a type-safe way.
A final
class can also be useful for enforcing a design restriction. For example, internal classes that aren't meant to be derived from ever could be declared final
, so that incorrect derivations could be caught by the compiler.
I have realized that alternate semantics could be useful here: NoChildren
can be derived from, but all its virtual members (including those inherited from its base classes) are treated as final
. This means that the same optimizations could be performed (resolving virtual calls at compile-time), but the class wouldn't needlessly be restricted for derivation. How could a derived class hurt?
class Base {
public:
virtual void f();
};
final class Derived : public Base {
public:
virtual void f();
};
class Other : public Derived {
public:
void x() {
f(); // can be optimized to Derived::f()
// etc.
}
void f(); // does *not* override Derived::f()
};
However, these semantics can be achieved by using the "base" form of final
:
class Base {
public:
virtual void f();
};
class Derived : public final Base {
public:
final void f();
};
class Other : public Derived {
public:
void x() {
f(); // can be optimized to Derived::f()
// etc.
}
void f(); // does *not* override Derived::f()
};
A final
virtual
member cannot be overridden in a derived class.
class Base {
public:
virtual void f();
};
class Derived : public Base {
public:
final virtual void f();
final void g(); // error - doesn't override anything
};
Derived::f()
cannot be overridden in a derived class. For all intents and purposes, it behaves as if it were a non-virtual function in Derived
(while still overriding Base::f()
).
This would be useful for preventing derived classes from accidentally overriding internal base class virtual functions:
class InternalBase {
virtual void internal_func() = 0;
};
class Normal : InternalBase {
final void internal_func(); // don't allow derived classes to override
// ...
};
class User : public Normal {
void internal_func(); // no danger of overriding - good
};
This prevents users from accidentally overriding, and allows the compiler to optimize in some cases. The "override
" construct would have also prevented the error - User
wouldn't have declared internal_func()
as override
, and thus would have gotten a compilation error (this is assuming final
wasn't used - just override
).
With regards to code validity, a final
member function would be similar to the "override
" construct I detail, in that it would be an error to declare a member function final
that isn't actually overriding any base class virtual functions.
class Base {
public:
virtual void f();
};
class Derived : public Base {
public:
final void f(); // OK
final void g(); // error - nothing is being overridden
};
A reference to a final
-qualified type must refer to an object of that complete type - nothing derived from it.
class Base {
public:
virtual void f();
};
class Derived : public Base {
public:
virtual void f();
};
void f( Base final& base ) {
base.f(); // can be optimized to directly call Base::f()
}
void g() {
Base base;
f( base ); // OK - complete type is Base
Base& base_ref = base;
f( base_ref ); // error - complete type isn't necessarily Base
Derived der;
f( der ); // error - complete type isn't Base
}
A final
reference must refer to an object whose complete type is the same as that the reference refers to, as illustrated above. This behavior is similar to const
, in that a non-const
reference cannot be bound to an object that is const
(without a cast). In this case, a final
reference cannot be bound to a non-final
type.
Similar to const
, users must be final
-correct or else there will be problems. You know how some people moan and complain about being const
-correct? Now they have another issue to complain about!
Complete objects always have a final
type. For example:
void g( Base& base ) {
Base final& fb = base; // error - base may refer to derived type
Base base;
Base final& fb2 = base; // OK - base is a complete object
Base final* p = new Base; // OK - new object has complete type of Base
}
class X {
public:
X() {
X final& ref = *this; // OK - complete type is X
}
~X() {
X final& ref = *this; // OK - complete type is X
}
};
A nice side-effect of this is that it means the compiler will automatically perform virtual optimizations when the complete type of the object is know - i.e. any complete object (global variables, local variables, member variables).
Built-in types and references to classes declared final
also have a final
type:
final class NoDerived : public Foo {
// ...
};
void f( NoDerived& fin, int& num ) {
NoDerived final& = fin; // OK - couldn't refer to anything derived
int final& fnum = num; // OK - nothing can be derived
}
A use of a reference to final
would be to prevent accidental "slicing".
// returns data in an argument instead of returning it
// we don't want to incur the overhead of copying Base, so take a ref
void get_data( Base final& );
void user() {
Base base;
get_data( base ); // OK
Derived der;
get_data( der ); // error
}
An example of sort of the reverse of slicing is in containers that hold their items by value:
template<typename T>
class container {
T* items;
public:
T final& operator [] ( int i ) { return items [i]; }
};
Here, final
serves a couple of purposes - it clearly says that operator [] returns a reference to the complete object type, thus showing that no slicing is going on. It allows optimizations to be made since the complete type of the object is known.
Another example, a low-level function that takes a C-style array of objects:
struct Foo {
// ...
};
struct Bar : Foo {
// ...
};
void f( Foo final* items, int size );
void g() {
Foo foo [10];
f( foo, 10 ); // OK
Bar bar [10];
f( bar, 10 ); // error
}
This is a nice way to prevent the dreaded "sliced array" problem at compile-time.
This would be useful where assumptions were made about the type of object, like assuming nothing is derived from it because it is being deleted, and doesn't have a virtual
destructor:
struct Foo {
~Foo();
// ...
};
void f( Foo final* foo ) {
if ( /* ... */ )
delete foo;
}
This prevents undefined behavior at run-time by catching derived types at compile-time.
We could have a final_cast<>
construct that was similar to const_cast<>
void f( Base final& );
void g( Base& base ) {
f( base ); // error
f( final_cast<Base final&> (base) ); // OK
}
void h() {
Base base;
g( base ); // OK
Derived der;
g( der ); // bad
}
A base class declared final
has the effect of making final
all inherited virtual members in the derived class.
class Mixin {
public:
virtual void f();
virtual void g();
};
class Derived : final Mixin {
private:
virtual void f();
// didn't override g()
};
The effect is that all virtual members inherited from Mixin are final
. For example, a class derived from Derived
cannot accidentally override Derived::f
- the most it can do is hide it (note - perhaps it should be an error to try to hide a base virtual function in this manner, as it could be confusing).
As the example shows, this would be useful for mixin classes where we don't want the virtual
members to be overridable in derived classes. The same effect could be achieved without this "base" use by overriding all virtual functions from the mixin, and declaring them final
, but this would be tedious and error-prone (what if the mixin added new virtual functions?).