B
has a virtual function f
, and
the user declares their own f
in derived class
D
, thereby accidentally overriding B::f
.
B
has a virtual function f
, and
the user wishes to override that function in the derived class
D
, but misspells the name as D::foo
.
B
has a virtual function f
, and
the user wishes to override that function in the derived class
D
, but inadvertently alters the parameter list.
B1
has a virtual function f
, another base class
B2
also has a virtual function f
with the same
signature, the user derives a class D
from both
B1
and B2
and declares D::f
to
override both B1::f
and B2::f
, one of which may
have been inadvertent. (This is sometimes referred to as the Siamese Twins
problem.)
The scope of the problem was reduced to the minimal necessary to solve the common real-world problems. This means it does not tackle ideas such as 'final overriders' or function renaming, both of which would be available if a scheme closer to the C++/CLI standard was pursued.
N2108 was presented at the Portland meeting and introduced the concept of an explicit polymorphic class as a refinement of the existing polymorphic class type. The virtual function syntax wording could then be updated to use the new class type, while deferring the final choice of syntax to mark the class as explicit to a later paper. Feedback was to advance to Core once a suitable markup could be found.
This paper adopts the notation of the attributes proposal for C++0x in N2236. It also includes feedback since Portland to address name-hiding in addition to the virtual function issues.
[[check_names]]
attribute for class
definitions. Use of this attribute turns on additional checks that the
member functions declared in the class do not conflict with the those of
names in any base class.
In particular, the "virtual" keyword must be used when overriding virtual
functions, and named declared in a base class cannot be hidden. Two
further attributes are introduced that allow specific functions to disable
the checks. [[new]]
indicates that a virtual function is
being introduced for the first time, and [[hiding]]
indicates a deliberate attempt to hide names from a base class.
This design meets three key compatibility requirements:
It also meets an additional goal that the number of attribute mark-ups in typical use is small, often no more than a single attribute on the class definition. This is based on an assumption that name hiding is rarely intentional, and will be typically fixed with a using declaration. It is also assumed that virtual functions are more frequently overriden than introduced. This is especially true with the common idiom of an 'interface class' where only root-classes introduces virtual functions. Note that roots, having no base classes, should not be marked up in practice as there is nothing to check for classes without a base.
The 'simple' syntax for overriding virtual functions in C++ today is convenient, but potentially misleading. Take the following example:
struct base { virtual void some_func(); }; struct derived { void some_func(); };
Is the use of some_func in derived intentionally an override, or an accidental name clash? Without clear documentation or a detailed analysis of the use/implementation it is hard to be sure.
The common assumption is that all such use is deliberate, and to ignore the question. Yet while it is reasonable to assume that competent developers would not accidentally make such a mistake, it is easily introduced by dependencies on third party libraries. Maintenance of the base classes might introduce such a clash without clear notice in the code, and the ensuing bugs can be very hard to track down. Worse, even a rigorous set of unit tests does not guarantee protection, as it is the base class, not the derived, that is changing behaviour and so the effects are not guaranteed to be covered by existing tests. Likewise, source analysis tools can offer little help here without an additional hint from the coder. To that end, we offer the [[check_names]] attribute, and the requirement in a checked class that intentional overrides use the virtual keyword, which is remains optional when the attribute is not used.
struct base { virtual void some_func(); }; struct derived [[check_names]] { void some_func(); // error, accidental override with check_names attribute virtual void some_func(); // OK, override with virtual keyword };
A second set of problems arise when a user intends to override a function, but gets the signature wrong in some way. Common examples are mis-spelling the name (transposing two characters, bad capitalization); using a the wrong datatype (double instead of float); or missing the cv-qualification.
All these classes of errors can be detected in the same way. When the virtual keyword is used in a class defined with the [[check_names]] attribute, a matching declaration must be found in one of the base classes for it to override. In implementation terms it would suffice to check the inherited vtable, rather than search the full DAG of base classes looking for a match.
Note: This kind of problem should also be detected with a good set of unit tests. It is hard to solve in a lexical analysis tool without a further hint, see next point!
struct base { virtual void some_func1(); virtual void some_func2(float); virtual void some_func3() const; virtual long some_func4(int); }; struct derived [[check_names]] { virtual void sone_func1(); // error, mis-spelled name virtual void some_func2(double); // error, no bad argument type virtual void some_func3(); // error, missing cv-qualification virtual int some_func4(int); // ill-formed: return type does not match B::h };
The problem with checking for functions to override is that is becomes impossible to introduce a new virtual function into the hierarchy in a checked class. We propose to solve this with the [[new]]
attribute. This is used with virtual function declations to:
This kind of check is hard to cover with unit tests, and can only be caught by lexical analysis tools with an additional mark-up, such as we propose here.
struct base { virtual void some_func1(); }; struct derived [[check_names]] { virtual void some_func1(); [[new]] // error, accidental override virtual void some_func2(); [[new]] // OK, new virtual function introduced };
Another problem that catches people out when defining classes is that introducing a name will hide occurences of the same name from any bases classes. Sometimes this is intentional, but there is little chance of a warning when it occurs accidentally. The language-supplied workaround in those cases is a using declaration, but you only apply the workaround when you are aware of the problem. The [[check_names]]
attribute will require the compiler inform you of accidental hiding. The [[hiding]]
attribute will allow you to tell the compiler that the hiding is intentional, and the program remains well-formed.
Example:
class B { virtual void f(); virtual void h(int); void j(int); void k(int); }; class D [[check_names]] : public B { using B::j; virtual void f(); // OK: overrides B::f virtual void g(long); // ill-formed: new virtual function introduced void h(int); // ill-formed: overriding without "virtual" virtual void h(long); // ill-formed: new virtual function introduced virtual void h(double); [[new]] // ill-formed: new virtual function hides void h(int) virtual void h(char *); [[new, hiding]] // OK virtual int j( double ); // OK, using declaration prevents hiding int k( double ); // ill-formed: name hiding and no using declaration double k( char * ) [[hiding]]; // OK, hiding is clearly indicated };
7.6.5 Attributes to check names of class membersReplace in 10.3p2The attribute-token
check_names
specifies that a class strictly checks overriding and hiding of base member functions. It shall appear at most once in each attribute-list and no attribute-argument-clause shall be present. The attribute applies to a class definition, see 10.3 class.virtual.The attribute-token
new
specifies that a virtual member function does not override a function in a base class.The attribute-token
hiding
specifies that the name of a member function hides another member function in a base class.Each of the attribute-tokens
new
andhiding
shall appear at most once in each attribute-list and no attribute-argument-clause shall be present for either. The attributes apply to the function type of a member function declaration, but the type of the member function is unchanged. The attribute-tokennew
applies only to the type of a virtual member function.
If a virtual member function vf is declared in a classbyBase
and in a classDerived
, derived directly or indirectly fromBase
, a member functionvf
with the same name, parameter-type-list (8.3.5), and cv-qualification asBase::vf
is declared, thenDerived::vf
is also virtual ( whether or not it is so declared) and it overridesBase::vf
.
If a virtual member functionAdd a new paragraph to 3.3.8 (basic.scope.hiding)vf
is declared in a classB
and in a classD
, derived directly or indirectly fromB
, a member functionvf
with the same name is declared,D::vf
may overrideB::vf
as follows: IfD
is not marked with thecheck_names
attribute and the parameter-type-list (8.3.5) and cv-qualification ofD::vf
are the same as those ofB::vf
, thenD::vf
is also virtual (whether or not it is so declared) and it overridesB::vf
. IfD
is marked with thecheck_names
attribute, thenD::vf
shall be declared virtual and, if the parameter-type-list (8.3.5) and cv-qualification ofD::vf
are the same as those ofB::vf
,D::vf
overridesB::vf
. In a class marked with thecheck_names
attribute, a virtual member function shall be marked with thenew
attribute if and only if it does not override one in a base class .
If a member-declaration hides a member that would otherwise be visible in a base class, that member-declaration shall be decorated with thehiding
attribute (7.6.5 dcl.attr.checks). [Note: A using directive makes the potentially hidden name visible, avoiding the need for thehiding
attribute.]