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 in one of multiple parents, which can result in a silent change to the semantics of an existing program, as shown in the following example.

struct A {
    virtual void foo ();
};

struct B : virtual A { };

struct C : virtual A {
    void foo () override; // late addition
};

struct D : B, C 
{
    void bar () {
        foo ();  // calls the late addition
    }
};

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. Multiple inheritance adds an additional dimension since a class can inherit the same base class A indirectly via different direct base classes and can result in multiple base class sub-objects of type A, which can result in ambiguous code.

If all classes in a multiple inheritance hierarchy that directly inherit from A use the keyword virtual when inheriting from A, there will be only one base class sub-object of type A in the most derived object. Inconsistently inheriting from A with and without virtual is confusing, because 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.

C++ favours value-semantics for copy and move operations as well as destruction that conflicts with object-oriented polymorphic behaviour.

If a “using-declaration” refers to a constructor of a direct base class, all constructors of that base are candidates for the initialization of the derived class. If a base class constructor is selected to initialize the derived type, any other bases or members of the derived type will be initialized as if by an = default default constructor. This can leave some members uninitialzed, that can result in undefined behaviour.

If a using-declaration refers to a protected member of a base class, its accessibility can be changed, which destroys the encapsulation of the member.

If a base class overloads operator new and operator delete, any derived classes will inherit those operations. 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 can result in undefined behaviour.

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: