Virtual inheritance

Last updated
Diagram of diamond inheritance, a problem that virtual inheritance is trying to solve. Diamond inheritance.svg
Diagram of diamond inheritance, a problem that virtual inheritance is trying to solve.

Virtual inheritance is a C++ technique that ensures only one copy of a base class 's member variables are inherited by grandchild derived classes. Without virtual inheritance, if two classes B and C inherit from a class A, and a class D inherits from both B and C, then D will contain two copies of A's member variables: one via B, and one via C. These will be accessible independently, using scope resolution.

Contents

Instead, if classes B and C inherit virtually from class A, then objects of class D will contain only one set of the member variables from class A.

This feature is most useful for multiple inheritance, as it makes the virtual base a common subobject for the deriving class and all classes that are derived from it. This can be used to avoid the diamond problem by clarifying ambiguity over which ancestor class to use, as from the perspective of the deriving class (D in the example above) the virtual base (A) acts as though it were the direct base class of D, not a class derived indirectly through a base (B or C). [1] [2]

It is used when inheritance represents restriction of a set rather than composition of parts. In C++, a base class intended to be common throughout the hierarchy is denoted as virtual with the virtual keyword.

Consider the following class hierarchy.

UML virtual inheritance.svg

structAnimal{virtual~Animal()=default;// Explicitly show that the default class destructor will be made.virtualvoidEat(){}};structMammal:Animal{virtualvoidBreathe(){}};structWingedAnimal:Animal{virtualvoidFlap(){}};// A bat is a winged mammalstructBat:Mammal,WingedAnimal{};

As declared above, a call to bat.Eat is ambiguous because there are two Animal (indirect) base classes in Bat, so any Bat object has two different Animal base class subobjects. So, an attempt to directly bind a reference to the Animal subobject of a Bat object would fail, since the binding is inherently ambiguous:

Batbat;Animal&animal=bat;// error: which Animal subobject should a Bat cast into, // a Mammal::Animal or a WingedAnimal::Animal?

To disambiguate, one would have to explicitly convert bat to either base class subobject:

Batbat;Animal&mammal=static_cast<Mammal&>(bat);Animal&winged=static_cast<WingedAnimal&>(bat);

In order to call Eat, the same disambiguation, or explicit qualification is needed: static_cast<Mammal&>(bat).Eat() or static_cast<WingedAnimal&>(bat).Eat() or alternatively bat.Mammal::Eat() and bat.WingedAnimal::Eat(). Explicit qualification not only uses an easier, uniform syntax for both pointers and objects but also allows for static dispatch, so it would arguably be the preferable method.

In this case, the double inheritance of Animal is probably unwanted, as we want to model that the relation (Bat is an Animal) exists only once; that a Bat is a Mammal and is a WingedAnimal, does not imply that it is an Animal twice: an Animal base class corresponds to a contract that Bat implements (the "is a" relationship above really means "implements the requirements of"), and a Bat only implements the Animal contract once. The real world meaning of "is a only once" is that Bat should have only one way of implementing Eat, not two different ways, depending on whether the Mammal view of the Bat is eating, or the WingedAnimal view of the Bat. (In the first code example we see that Eat is not overridden in either Mammal or WingedAnimal, so the two Animal subobjects will actually behave the same, but this is just a degenerate case, and that does not make a difference from the C++ point of view.)

This situation is sometimes referred to as diamond inheritance (see Diamond problem) because the inheritance diagram is in the shape of a diamond. Virtual inheritance can help to solve this problem.

The solution

We can re-declare our classes as follows:

structAnimal{virtual~Animal()=default;virtualvoidEat(){}};// Two classes virtually inheriting Animal:structMammal:virtualAnimal{virtualvoidBreathe(){}};structWingedAnimal:virtualAnimal{virtualvoidFlap(){}};// A bat is still a winged mammalstructBat:Mammal,WingedAnimal{};

The Animal portion of Bat::WingedAnimal is now the sameAnimal instance as the one used by Bat::Mammal, which is to say that a Bat has only one, shared, Animal instance in its representation and so a call to Bat::Eat is unambiguous. Additionally, a direct cast from Bat to Animal is also unambiguous, now that there exists only one Animal instance which Bat could be converted to.

The ability to share a single instance of the Animal parent between Mammal and WingedAnimal is enabled by recording the memory offset between the Mammal or WingedAnimal members and those of the base Animal within the derived class. However this offset can in the general case only be known at runtime, thus Bat must become (vpointer, Mammal, vpointer, WingedAnimal, Bat, Animal). There are two vtable pointers, one per inheritance hierarchy that virtually inherits Animal. In this example, one for Mammal and one for WingedAnimal. The object size has therefore increased by two pointers, but now there is only one Animal and no ambiguity. All objects of type Bat will use the same vpointers, but each Bat object will contain its own unique Animal object. If another class inherits from Mammal, such as Squirrel, then the vpointer in the Mammal part of Squirrel will generally be different to the vpointer in the Mammal part of Bat though they may happen to be the same if the Squirrel class be the same size as Bat.

Additional Example of Several Ancestors

This example to illustrates a case where base class A has a constructor variable msg and an additional ancestor E is derived from grandchild class D.

  A    / \   B   C    \ /     D    |   E 

Here, A must be constructed in both D and E. Further, inspection of the variable msg illustrates how class A becomes a direct base class of its deriving class, as opposed to a base class of any intermediate deriving classed between A and the final deriving class. The code below may be explored interactively here.

#include<string>#include<iostream>classA{private:std::string_msg;public:A(std::stringx):_msg(x){}voidtest(){std::cout<<"hello from A: "<<_msg<<"\n";}};// B,C inherit A virtuallyclassB:virtualpublicA{public:B(std::stringx):A("b"){}};classC:virtualpublicA{public:C(std::stringx):A("c"){}};// Compile error when :A("c") is removed (since A's constructor is not called)//class C: virtual public A   { public: C(std::string x){}  };//class C: virtual public A   { public: C(std::string x){ A("c"); }  }; // Same compile error// Since B, C inherit A virtually, A must be constructed in each childclassD:publicB,C{public:D(std::stringx):A("d_a"),B("d_b"),C("d_c"){}};classE:publicD{public:E(std::stringx):A("e_a"),D("e_d"){}};// Compile error without constructing A//class D: public         B,C { public: D(std::string x):B(x),C(x){}  };// Compile error without constructing A//class E: public         D   { public: E(std::string x):D(x){}  };intmain(intargc,char**argv){Dd("d");d.test();// hello from A: d_aEe("e");e.test();// hello from A: e_a}

Pure Virtual Methods

Suppose a pure virtual method is defined in the base class. If a deriving class inherits the base class virtually, then the pure virtual method does not need to be defined in that deriving class. However, if the deriving class does not inherit the base class virtually, then all virtual methods must be defined. The code below may be explored interactively here.

#include<string>#include<iostream>classA{protected:std::string_msg;public:A(std::stringx):_msg(x){}voidtest(){std::cout<<"hello from A: "<<_msg<<"\n";}virtualvoidpure_virtual_test()=0;};// since B,C inherit A virtually, the pure virtual method pure_virtual_test doesn't need to be definedclassB:virtualpublicA{public:B(std::stringx):A("b"){}};classC:virtualpublicA{public:C(std::stringx):A("c"){}};// since B,C inherit A virtually, A must be constructed in each child// however, since D does not inherit B,C virtually, the pure virtual method in A *must be defined*classD:publicB,C{public:D(std::stringx):A("d_a"),B("d_b"),C("d_c"){}voidpure_virtual_test()override{std::cout<<"pure virtual hello from: "<<_msg<<"\n";}};// it is not necessary to redefine the pure virtual method after the parent defines itclassE:publicD{public:E(std::stringx):A("e_a"),D("e_d"){}};intmain(intargc,char**argv){Dd("d");d.test();// hello from A: d_ad.pure_virtual_test();// pure virtual hello from: d_aEe("e");e.test();// hello from A: e_ae.pure_virtual_test();// pure virtual hello from: e_a}

Related Research Articles

<span class="mw-page-title-main">C++</span> General-purpose programming language

C++ is a high-level, general-purpose programming language created by Danish computer scientist Bjarne Stroustrup. First released in 1985 as an extension of the C programming language, it has since expanded significantly over time; modern C++ currently has object-oriented, generic, and functional features, in addition to facilities for low-level memory manipulation. It is almost always implemented as a compiled language, and many vendors provide C++ compilers, including the Free Software Foundation, LLVM, Microsoft, Intel, Embarcadero, Oracle, and IBM.

In computer programming, lazy initialization is the tactic of delaying the creation of an object, the calculation of a value, or some other expensive process until the first time it is needed. It is a kind of lazy evaluation that refers specifically to the instantiation of objects or other resources.

In object-oriented programming, the decorator pattern is a design pattern that allows behavior to be added to an individual object, dynamically, without affecting the behavior of other objects from the same class. The decorator pattern is often useful for adhering to the Single Responsibility Principle, as it allows functionality to be divided between classes with unique areas of concern as well as to the Open-Closed Principle, by allowing the functionality of a class to be extended without being modified. Decorator use can be more efficient than subclassing, because an object's behavior can be augmented without defining an entirely new object.

Multiple dispatch or multimethods is a feature of some programming languages in which a function or method can be dynamically dispatched based on the run-time (dynamic) type or, in the more general case, some other attribute of more than one of its arguments. This is a generalization of single-dispatch polymorphism where a function or method call is dynamically dispatched based on the derived type of the object on which the method has been called. Multiple dispatch routes the dynamic dispatch to the implementing function or method using the combined characteristics of one or more arguments.

Template metaprogramming (TMP) is a metaprogramming technique in which templates are used by a compiler to generate temporary source code, which is merged by the compiler with the rest of the source code and then compiled. The output of these templates can include compile-time constants, data structures, and complete functions. The use of templates can be thought of as compile-time polymorphism. The technique is used by a number of languages, the best-known being C++, but also Curl, D, Nim, and XL.

<span class="mw-page-title-main">D (programming language)</span> Multi-paradigm system programming language

D, also known as dlang, is a multi-paradigm system programming language created by Walter Bright at Digital Mars and released in 2001. Andrei Alexandrescu joined the design and development effort in 2007. Though it originated as a re-engineering of C++, D is a profoundly different language —features of D can be considered streamlined and expanded-upon ideas from C++, however D also draws inspiration from other high-level programming languages, notably Java, Python, Ruby, C#, and Eiffel.

In object-oriented programming, in languages such as C++, and Object Pascal, a virtual function or virtual method is an inheritable and overridable function or method for which dynamic dispatch is facilitated. This concept is an important part of the (runtime) polymorphism portion of object-oriented programming (OOP). In short, a virtual function defines a target function to be executed, but the target might not be known at compile time.

In computer science, a tagged union, also called a variant, variant record, choice type, discriminated union, disjoint union, sum type or coproduct, is a data structure used to hold a value that could take on several different, but fixed, types. Only one of the types can be in use at any one time, and a tag field explicitly indicates which one is in use. It can be thought of as a type that has several "cases", each of which should be handled correctly when that type is manipulated. This is critical in defining recursive datatypes, in which some component of a value may have the same type as that value, for example in defining a type for representing trees, where it is necessary to distinguish multi-node subtrees and leaves. Like ordinary unions, tagged unions can save storage by overlapping storage areas for each type, since only one is in use at a time.

In computer programming, run-time type information or run-time type identification (RTTI) is a feature of some programming languages that exposes information about an object's data type at runtime. Run-time type information may be available for all types or only to types that explicitly have it. Run-time type information is a specialization of a more general concept called type introspection.

<span class="mw-page-title-main">Method overriding</span> Language feature in object-oriented programming

Method overriding, in object-oriented programming, is a language feature that allows a subclass or child class to provide a specific implementation of a method that is already provided by one of its superclasses or parent classes. In addition to providing data-driven algorithm-determined parameters across virtual network interfaces, it also allows for a specific type of polymorphism (subtyping). The implementation in the subclass overrides (replaces) the implementation in the superclass by providing a method that has same name, same parameters or signature, and same return type as the method in the parent class. The version of a method that is executed will be determined by the object that is used to invoke it. If an object of a parent class is used to invoke the method, then the version in the parent class will be executed, but if an object of the subclass is used to invoke the method, then the version in the child class will be executed. This helps in preventing problems associated with differential relay analytics which would otherwise rely on a framework in which method overriding might be obviated. Some languages allow a programmer to prevent a method from being overridden.

<i>Modern C++ Design</i> Book by Andrei Alexandrescu

Modern C++ Design: Generic Programming and Design Patterns Applied is a book written by Andrei Alexandrescu, published in 2001 by Addison-Wesley. It has been regarded as "one of the most important C++ books" by Scott Meyers.

A class in C++ is a user-defined type or data structure declared with keyword class that has data and functions as its members whose access is governed by the three access specifiers private, protected or public. By default access to members of a C++ class is private. The private members are not accessible outside the class; they can be accessed only through methods of the class. The public members form an interface to the class and are accessible outside the class. #line is used to determine the number of lines in given file.

The curiously recurring template pattern (CRTP) is an idiom, originally in C++, in which a class X derives from a class template instantiation using X itself as a template argument. More generally it is known as F-bound polymorphism, and it is a form of F-bounded quantification.

C++11 is a version of the ISO/IEC 14882 standard for the C++ programming language. C++11 replaced the prior version of the C++ standard, called C++03, and was later replaced by C++14. The name follows the tradition of naming language versions by the publication year of the specification, though it was formerly named C++0x because it was expected to be published before 2010.

In object-oriented computer programming, a null object is an object with no referenced value or with defined neutral (null) behavior. The null object design pattern, which describes the uses of such objects and their behavior, was first published as "Void Value" and later in the Pattern Languages of Program Design book series as "Null Object".

In computing, compile-time function execution is the ability of a compiler, that would normally compile a function to machine code and execute it at run time, to execute the function at compile time. This is possible if the arguments to the function are known at compile time, and the function does not make any reference to or attempt to modify any global state.

In object-oriented programming, a virtual base class is a nested inner class whose functions and member variables can be overridden and redefined by subclasses of an outer class. Virtual classes are analogous to virtual functions.

This article describes the syntax of the C# programming language. The features described are compatible with .NET Framework and Mono.

In the C++ programming language, dominance refers to a particular aspect of C++ name lookup in the presence of Inheritance. When the compiler computes the set of declarations to which a particular name might refer, declarations in very-ancestral classes which are "dominated" by declarations in less-ancestral classes are hidden for the purposes of name lookup. In other languages or contexts, the same principle may be referred to as "name masking" or "shadowing".

In computer programming, variadic templates are templates that take a variable number of arguments.

References

  1. Milea, Andrei. "Solving the Diamond Problem with Virtual Inheritance". Cprogramming.com. Retrieved 2010-03-08. One of the problems that arises due to multiple inheritance is the diamond problem. A classical illustration of this is given by Bjarne Stroustrup (the creator of C++) in the following example:
  2. McArdell, Ralph (2004-02-14). "C++/What is virtual inheritance?". All Experts. Archived from the original on 2010-01-10. Retrieved 2010-03-08. This is something you find may be required if you are using multiple inheritance. In that case it is possible for a class to be derived from other classes which have the same base class. In such cases, without virtual inheritance, your objects will contain more than one subobject of the base type the base classes share. Whether this is what is the required effect depends on the circumstances. If it is not then you can use virtual inheritance by specifying virtual base classes for those base types for which a whole object should only contain one such base class subobject.