parts/6.41.Inheritance-RIP.md

6.41 Inheritance [RIP]

6.41.1 Applicability to language

The vulnerability as described in ISO/IEC 24772-1:2024 6.41 is applicable to C++.

In C++, inheritance is defined differently than in most other languages that support inheritance, e.g.: - There is no implicit common base class. - Defining a member with a name that is defined in a base class causes hiding the base class member in the derived class including all overloads with that name. (Full qualification via the base class name is required to access these hidden members.) - Dynamic polymorphism requires the use of references or pointers. - Private virtual member functions can be overridden in derived classes.

A vulnerability similar to namespace issues (see [BJL]) can arise if a new final overrider is defined, which can result in a silent change to the semantics of an existing program.

The implementation of virtual member functions does not take access into account. This can lead to a potentially surprising situation where a virtual function overrides an otherwise inaccessible base class function. This is quite different than the situation for non-virtual private member functions, which cannot be accessed from outside the declaring class or any friends.

Inheriting from a non-polymorphic base class can be used to implement adaptor types, i.e., by limiting or extending the base’s functionality. Using private inheritance can prevent treating the derived type object as a base type object. Inheriting constructors and using declarations ease re-promoting the scope of otherwise hidden members of the base.

Multiple inheritance adds an additional dimension since a class can inherit the same base class A indirectly via different direct base classes. Without any special preparation, this means the base class A object exists multiple times. Addressing members of A explicitly requires to specify the differentiating base class as a prefix, otherwise the code will be ambiguous. If all classes in such a multiple inheritance hierarchy that directly inherit from A use the keyword virtual when inheriting from A, there will be only one object of type A in the most derived object. Inconsistently inheriting from A with and without virtual might lead to confusing behavior, because still multiple base objects of type A exist. Even when an ambiguity does not currently exist, refering to a base class member using its qualified name reduces the chance that a future modification silently changes the program’s behaviour through accessing a different member with the same name.

In C++, a name declared in a virtual base and inherited multiple times but hidden on any virtual path is considered to be hidden on all virtual paths. Consequently, a hiding declaration introduced later will silently alter the semantics of code refering to this name. The vulnerability does not arise if a multiply-inherited name is found on at least one non-virtual inheritance path, since the use of the name is diagnosed as ambiguous.

Inheriting from base classes without virtual member functions and without non-static data members (empty bases) does not suffer from the multiple object problem of multiple inheritance, because the empty base class object will be omitted (elided) by the compiler (empty base class optimization). Such empty bases are often used to mix-in functionality into derived classes.

The compiler-provided default behaviour for copy and move operations as well as destruction favors value semantics which conflicts with object-oriented polymorphic behaviour.

If a base class overloads operator new and operator delete, any derived classes will inherit and therefore will use such. If the base class’ operator new and/or operator delete assume the size of the objects being allocated are all the size of the base class and they are not all the same size, then this will result in undefined behaviour such as access errors to memory that wasn’t allocated, overwriting of memory (if there are regions of memory immediately after the last byte allocated), memory leaks, etc.

The mechanisms of failure from ISO/IEC 24772-1:2024 clause 6.41 can be mitigated in C++ as follows:

6.41.2 Avoidance mechanisms for language users

To avoid the vulnerability or mitigate its ill effects, C++ software developers can: