C++ Inheritance

Inheritance is one of the most important principles of object-oriented programming. In C++, inheritance takes place between classes wherein one class acquires or inherits properties of another class. The newly defined class is known as derived class and the class from which it inherits is called the base class. Class inheritance reflects heredity in the natural world, where characteristics are transferred from parents to their children.

In C++, there are many types of inheritance namely, single, multiple, multilevel, hierarchical, and hybrid. C++ also supports different modes of inheritance. These are public, private, and protected.

Inheritance promotes code reuse. Reusing code not only makes code easy to understand but also reduces redundancy. Code maintenance is easier. Code reliability is improved.

Discussion

  • Could you explain C++ inheritance with an example?

    Consider two different classes, Dog and Cat. Both classes might have similar attributes such as breed and colour. In C++, such attributes are called data members. The classes might also have similar behaviours or actions such as eat, sleep and speak. In C++, such actions are implemented as member functions. Instead of writing similar code in two different classes we can create an Animal class that brings together common attributes and behaviours. Classes Dog and Cat can inherit from Animal class. They can add their own functionalities.

    In this example, Animal is the base class, Dog and Cat are derived classes. We may say that (Animal, Dog) represents a parent-child relationship. Dogs are inheriting characteristics of animals. We may also say that Animal is a generalization of Dog and Cat; and Dog and Cat are specializations of Animal.

    Derived classes can specialize by adding extra data members and member functions. They can also override the general behaviour defined in the base class.

    Inheritance in C++ follows a bottom up approach. Derived classes acquire properties from base classes but the reverse is not true.

  • What are different types of inheritance in C++?
    Types of inheritance supported in C++. Source: Adapted from Chauhan 2021.
    Types of inheritance supported in C++. Source: Adapted from Chauhan 2021.

    C++ supports many types of inheritance based on the number of base or derived classes and the class hierarchy:

    • Single Inheritance: A derived class inherits from only one base class. Eg. class B inherits from class A.
    • Multiple Inheritance: A derived class inherits from more than one base class. Eg. class C inherits from both class A and class B.
    • Multilevel Inheritance: The class hierarchy is deeper that one level. Eg. class C inherits from class B that itself is inherited from class A.
    • Multipath Inheritance: A derived class inherits from another derived class and also directly from the base class of the latter. Eg. class C inherits from class B and class A when class B itself is inherited from class A.

    While the above types are about a single class, there are others that are about the structure of the class hierarchy. These are more design patterns than types:

    • Hierarchical Inheritance: A base class is specialized in many derived classes, which in turn are specialized into more derived classes.
    • Hybrid Inheritance: This combines both multiple and hierarchical inheritance.
  • How does C++ deal with the diamond problem or ambiguity?
    Ambiguities in C++ due to multiple inheritance. Source: Adapted from IBM 2021, pp. 303-305.
    Ambiguities in C++ due to multiple inheritance. Source: Adapted from IBM 2021, pp. 303-305.

    The diamond ambiguity can occur with multiple inheritance. It happens when the base classes themselves are derived from another common base class. The final derived class ends up with multiple copies of the distant base class.

    Consider D inheriting from classes B1 and B2, B1 inheriting from A, and B2 inheriting from A. Thus, D has multiple copies of A. The problem occurs when a call is made to a member of A from an instance of D. C++ compiler can't determine which copy of A to use. Compilation will fail.

    To solve this problem, C++ uses the concept of virtual inheritance. When inheriting A, B1 and B2 will specify the virtual keyword. The compiler sees this and creates a single instance or copy of A within D. No virtual keyword is needed when defining D.

    Having multiple copies of a base class is not really an error. In fact, it's possible to define a class that uses both virtual and non-virtual inheritance. Ultimately, the compiler should be able to determine member access unambiguously.

  • What's the concept of name hiding in C++?
    Illustrating naming hiding in C++. Source: Adapted from IBM 2021, pp. 307.
    Illustrating naming hiding in C++. Source: Adapted from IBM 2021, pp. 307.

    Name hiding happens when a derived class has a member with the same name as a member of the base class. The derived class definition hides the base class definition. Thus, Animal::age is hidden by Dog::age.

    The concept is also related to but different from overriding. For example, Dog::speak() if defined overrides Animal::speak(). In addition, Dog::speak() can explicitly call the base class implementation with the statement Animal::speak().

    C++ also allows functions to be overloaded, which is about having multiple member functions of the same name that differ in their parameter types. So, we could have Animal::speak() and the Dog class that defines only Dog::speak(const string&). Unfortunately, in this case, Dog class doesn't have access to Animal::speak(). In this case, we have name hiding, not overriding.

    It's possible for Dog class to have access to Animal::speak() by simply including the line using Animal::speak; in its class definition. With this using directive, all the overloaded speak() functions of the base class become accessible in the derived class.

  • What is object slicing in C++?
    Taking a slice of the derived object. Source: Segal 2021.
    Taking a slice of the derived object. Source: Segal 2021.

    When a derived class object is typecast into one of its base classes, we're doing what's called object slicing. Conceptually, it's similar to typecasting a decimal number to an integer type, whereby the number loses it's decimal portion. With object slicing, the object loses members specialized in the derived class and retains only those parts of the base class to which it's been typecast.

    Consider a base class A and a derived class B. Consider object b of type B. Object slicing happens with the assignments A a = b and A& a_ref = b. Assignment operator is not virtual in C++. Hence, in these assignments the assignment operator of A is called and not that of B.

    The figure shows another example. Base class defines data member a and virtual functions bar1() and bar2(). Derived class overrides bar1(), and adds bar3(), bar4() and b. An A-type slice of B can access a, derived class function bar1() and base class function bar2().

  • What are compile-time and runtime bindings in the context of inheritance?
    Runtime binding is implemented in C++ using vpointers and vtables. Source: Arias 2017.
    Runtime binding is implemented in C++ using vpointers and vtables. Source: Arias 2017.

    If the C++ compiler can determine what function to call at compile time, we call it compile-time binding, early binding or static dispatch. However, where virtual functions are defined in a base class and overridden by derived classes, the compiler can't know which function to call. This information is available only at runtime, giving rise to the terms runtime binding, late binding or dynamic dispatch.

    Suppose classes Square and Triangle are derived from Shape. Consider member functions virtual void Shape::area() {...}, void Square::area() {...} and void Triangle::area() {...}. Also defined is the function paintArea(Shape& shape) { int area = shape.area(); ... }. Exactly which area function is called inside paintArea()? This is known only at runtime.

    Under the hood, runtime binding relies on vpointers and vtables. Every class with virtual functions has a vtable that stores pointers to virtual function definitions. When a virtual function is overridden, the pointer would point to the derived class implementation. Every class with a vtable also has a vpointer that points to the correct vtable.

  • What are the rules of inheritance involving C++ abstract classes?

    An abstract class in C++ has at least one pure virtual function, which essentially defines the interface but doesn't supply an implementation. Abstract classes are meant to be used as base classes for other class definitions. Abstract classes themselves can't be instantiated. Their very purpose is inheritance that conforms to an interface.

    An abstract class can't be used as a parameter type, a function return type or the result of an explicit conversion. Pointers and references to an abstract class are allowed.

    If a derived class inherits from an abstract class without supplying implementations, the derived class is also an abstract class. Thus, "abstraction" can be inherited. On the other hand, it's possible to inherit from a non-abstract base class, add pure virtual functions or override a non-pure virtual function with a pure virtual function, and thereby define an abstract derived class.

    When a pure virtual function is called from a constructor, the behaviour is undefined.

  • What are the different visibility modes in inheritance in C++?
    Different visibility modes in C++ inheritance. Source: Adapted from Alex 2021.
    Different visibility modes in C++ inheritance. Source: Adapted from Alex 2021.

    C++ supports three access specifiers: public, protected and private. These access specifiers are used on data members and member functions. If not explicitly mentioned, private access is the default.

    Likewise, a derived class can use an access specifier on each of its base classes. Available access specifiers are public, protected, and private. Where multiple inheritance is used, each base class can have a different access specifier. If not explicitly mentioned, private inheritance is the default.

    Regardless of the access specifier on the base class, base class private members will remain private; that is, derived class can't access them. For public and protected members of base class, we summarize how access specifier on the base class affects access to members of the derived class:

    • Public: Public and protected members of base class become public and protected members of derived class respectively.
    • Protected: Public and protected members of base class become protected members of derived class.
    • Private: Public and protected members of base class become private members of derived class.
  • What's the influence of access specifiers on virtual functions?
    Illustrating the use of virtual functions and access specifiers. Source: Adapted from IBM 2021, pp. 313-314.
    Illustrating the use of virtual functions and access specifiers. Source: Adapted from IBM 2021, pp. 313-314.

    The figure shows three examples involving the member function A::name(). In (a), the function is non-virtual. Hence, when it's called via the base class pointer, A::name() is called. This is really compile-time binding.

    In (b), the function is made virtual. Now when the same call is made, runtime binding happens and B::name() is called. Although B::name() is private, it's called via the base class pointer and A::name() itself is public.

    In (c), we make the inheritance protected. This makes A::name() protected in the derived class. This means that it can't be called from outside the class, such as from main(). Hence we get a compile-time error just as if B::name() had been declared protected or private.

  • How is inheritance of struct different from that of class?

    Data structure struct comes from C programming language and is applicable in C++ as well. C++ extends struct so that it can include member functions. Like class, a struct definition can also be inherited.

    The main difference is that when access specifiers aren't specified, struct members are public by default whereas class members are private by default. Likewise, when access specifiers are omitted in inheritance, struct inheritance is public by default whereas class inheritance is private by default.

    For completeness, we note that there's also union that comes from C. It's members are public by default. Unions can't be used as base classes.

  • What are the workings of C++ class constructors and destructors?

    Constructors and destructors were traditionally not inherited. Since C++11, constructors could be inherited.

    Base class constructors are called before derived class constructors. In the case of multiple inheritance, base classes constructors are called in the depth-first left-to-right order of inheritance in the derived class. Destructors execute in reverse order.

    Constructors have be defined as public or protected so that derived class constructors can call base class constructors.

    Constructors need not be virtual: constructors are always called by name. Destructors have to be virtual. In other words, it's not sufficient to destroy only the base class object. We need to destroy the original derived class object.

  • What are the main criticisms of C++ inheritance?

    Inheritance in C++ is complex. Even a single inheritance has six variants: private/protected/public and virtual/non-virtual. With multiple inheritance, this complexity increases. The utility of a private virtual function is rather limited.

    The designer of a base class decides if a member function must be declared virtual. Derived class can't control this or prevent calls to base class non-virtual functions. Virtual inheritance is also a problem since the decision is made early. It prevents defining a derived class that wants two copies of the distant base class.

    C++ implementation of polymorphism via virtual functions can impact performance. Virtual member functions are not directly called. Instead, they've to be looked up via vpointer and vtable.

Milestones

Apr
1979

At Bell Laboratories, inspired by Simula, Bjarne Stroustrup conceives the idea of C with Classes. The new language would combine the low-level features of C and high-level code organization of Simula. Even in these early days, the language includes classes, class hierarchies and simple inheritance. Also included are private and public inheritance of a base class.

1983

To support runtime polymorphism, virtual functions are added to the language. Only with the introduction of virtual functions, the language claims to support object-oriented programming. This is also when the first implementation becomes available to users. Subsequently, the language is renamed to C++ (1984) and the first commercial release happens (1985).

Jun
1989

C++ 2.0 is released. This includes support for multiple inheritance and abstract class. Abstract classes provide a "cleaner separation between a user and an implementor" and reduces compile times.

Sep
2011

ISO publishes the C++11 standard, formally named ISO/IEC 14882:2011. This release introduces identifiers override and final. These help manage complex class hierarchies. They can be applied on virtual member functions when overridden in a derived class. Identifier final can also be applied to a class so that it can't be inherited. Constructors can now be inherited with the using declaration. This is useful when a derived object needs to be initialized in the same way as the base object.

Dec
2017

ISO publishes the C++17 standard, formally named ISO/IEC 14882:2017. It's now possible to do aggregate initialization involving derived and base classes.

Sample Code

  • //********** Single Inheritance *********************
     
    #include<iostream>
    using namespace std;
     
    class Mammal
    {
        public:
        Mammal()
        {
            cout<<"Mammal\n";
        }
    };
    class Animal:public Mammal 
    {
        public:
        Animal()
        {
            cout<<"Animal\n";
        }
    };
     
    int main()
    {
        Animal a;
        return 0;
    }
     
    /* output
    Mammal
    Animal*/ 
     
    //********** Multiple Inheritance *********************  
     
    #include <iostream>
    using namespace std;
     
    class Mammal
    {
        public:
        Mammal()
        {
            cout<<"Mammal\n";
        }
    };
     
    class Animal 
    {
        public:
        Animal()
        {
            cout<<"Animal\n";
        }
    };
     
    class Dog:public Mammal,public Animal
    {
        public:
        Dog()
        {
            cout<<"Dog\n";
        }
    };
     
    int main()
    {
        Dog d;
        return 0;
    }
     
    /* output
    Mammal
    Animal
    Dog*/
     
    //********** Multilevel Inheritance *********************  
     
    #include <iostream>
    using namespace std;
     
    class Mammal
    {
        public:
        Mammal()
        {
            cout<<"Mammal\n";
        }
    };
     
    class Animal:public Mammal 
    {
        public:
        Animal()
        {
            cout<<"Animal\n";
        }
    };
     
    class Dog:public Animal
    {
        public:
        Dog()
        {
            cout<<"Dog\n";
        }
    };
     
    int main()
    {
        Dog d;
        return 0;
    }
     
    /* output
    Mammal
    Animal
    Dog*/
     
    //********** Hierarchical Inheritance *********************  
     
    #include <iostream>
    using namespace std;
     
    class Animal
    {
        public:
        Animal()
        {
            cout<<"Animal\n";
        }
    };
     
    class Dog:public Animal
    {
        public:
        Dog()
        {
            cout<<"Dog\n";
        }
    };
     
    class Cat:public Animal
    {
        public:
        Cat()
        {
            cout<<"Cat\n";
        }
    };
     
    int main()
    {
        Dog d;
        Cat c;
        return 0;
    }
     
    /* output
    Animal
    Dog
    Animal
    Cat*/
     
    //********** Hybrid Inheritance *********************  
     
    #include <iostream>
    using namespace std;
     
    class Student
    {
        public:
        Student()
        {
            cout<<"Student\n";
        }
    };
    class Marks:public Student 
    {
        public:
        Marks()
        {
            cout<<"Marks\n";
        }
    };
     
    class Sports
    {
        public:
        Sports()
        {
            cout<<"Sports\n";
        }
    };
     
    class Result:public Marks, public Sports
    {
        public:
        Result()
        {
            cout<<"Result\n";
        }
    };
     
    int main()
    {
        Result r;
        return 0;
    }
     
    /*output
    Student
    Marks
    Sports
    Result*/
     

References

  1. Alex. 2021. "17.5 — Inheritance and access specifiers." Tutorial, Learn C++, August 2. Accessed 2022-01-05.
  2. Arias, Pablo. 2017. "Understanding Virtual Tables in C++." Blog, June 10. Accessed 2022-01-05.
  3. Arora, Preeti, and Pinky Gupta. 2016. "Computer Science with C++." Sultan Chand & Sons.
  4. Boccara, Jonathan. 2020. "Virtual, final and override in C++." Blog, Fluent C++, February 21. Accessed 2022-01-07.
  5. Cain, Jerry. 2007. "Advanced Inheritance and Virtual Methods." Handout 07, CS107L: Programming Paradigms Laboratory, Stanford University, November 16. Accessed 2021-12-27.
  6. Chauhan, Shailendra. 2021. "Understanding Inheritance and Different Types of Inheritance." DotNetTricks, August 31. Accessed 2022-01-05.
  7. Cppreference. 2021a. "C++ 11." Cppreference, November 10. Accessed 2021-12-29.
  8. Cppreference. 2021b. "Using-declaration." Cppreference, September 15. Accessed 2022-01-05.
  9. Cppreference. 2021c. "Access specifiers." Cppreference, November 16. Accessed 2022-01-05.
  10. Cppreference. 2021d. "C++ 17." Cppreference, October 4. Accessed 2021-12-29.
  11. Fertig, Andreas. 2019. "Using base class constructor." Blog, January 9. Accessed 2022-01-07.
  12. Frankomania. 2008. "What is object slicing?" StackOverflow, November 8. Accessed 2021-12-27.
  13. IBM. 2021. "z/OS XL C/C++ Language Reference." v2.5, IBM Corporation, September 12. Updated 2021-09-30. Accessed 2022-01-05.
  14. ISO. 2011. "ISO/IEC 14882:2011 Information technology — Programming languages — C++." Edition 3, September. Accessed 2022-01-07.
  15. ISO. 2017. "ISO/IEC 14882:2017 Information technology — Programming languages — C++." Edition 5, December. Accessed 2022-01-07.
  16. Joyner, Ian. 1992. "C++??: A Critique of C++." 2nd Edition. Accessed 2022-01-07.
  17. Meredith, Alisdair, Michael Wong, and Jens Maurer. 2008. "Inheriting Constructors (revision 5)." N2540=08-0050, JTC1/SC22/WG21 - The C++ Standards Committee - ISOCPP, February 29. Accessed 2022-01-07.
  18. Milea, Andrei. 2021. "Solving the Diamond Problem with Virtual Inheritance." Tutorial, Cprogramming.com. Accessed 2021-12-27.
  19. Rieck, Bastian. 2016. "Surprises with name hiding in C++." Blog, January 31. Accessed 2022-01-06.
  20. Roy, Nirmalya. 2012. "Inheritance." Slides, CptS 122 – Data Structures, Washington State University, October 19. Accessed 2022-01-06.
  21. Sakkinen, Markku. 1992. "A Critique of the Inheritance Principles of C++." Computing Systems, vol. 5, no. 1, pp. 69-110, Winter. Accessed 2022-01-07.
  22. Segal, Josh. 2021. "C++ Inheritance Memory Model." Geek Culture, on Medium, May 28. Accessed 2022-01-05.
  23. Smolsky, Oleg. 2015. "Extension to aggregate initialization." P0017R1, JTC1/SC22/WG21 - The C++ Standards Committee - ISOCPP, October 24. Accessed 2022-01-07.
  24. Stroustrup, Bjarne. 1993."A history of C++: 1979–1991." HOPL-II: The second ACM SIGPLAN conference on History of programming languages, pp. 271-297, April. doi: 10.1145/154766.155375. Accessed 2022-01-07.
  25. Stroustrup, Bjarne. 2007. "Evolving a language in and for the real world: C++ 1991-2006." HOPL III: Proceedings of the third ACM SIGPLAN conference on History of programming languages, pp. 4-1–4-59, June. doi: 10.1145/1238844.1238848. Accessed 2022-01-07.
  26. Stroustrup, Bjarne. 2020. "Thriving in a crowded and changing world: C++ 2006–2020." Proceedings of the ACM on Programming Languages, vol. 4, Issue HOPL, Article No. 70, pp. 1-168, June. Accessed 2022-01-07.
  27. Stroustrup, Bjarne. 2021. "Bjarne Stroustrup's FAQ." July 23. Accessed 2022-01-07.
  28. Wikipedia. 2021a. "Inheritance (object-oriented programming)." Wikipedia, November 14. Accessed 2021-11-27.

Further Reading

  1. IBM. 2021. "z/OS XL C/C++ Language Reference." v2.5, IBM Corporation, September 12. Updated 2021-09-30. Accessed 2022-01-05.
  2. Gray, Jan. 1994. "C++: Under the Hood." Microsoft. Accessed 2021-12-30.
  3. Roy, Nirmalya. 2012. "Inheritance." Slides, CptS 122 – Data Structures, Washington State University, October 19. Accessed 2022-01-06.
  4. Udacity. 2021. "C++ Inheritance Explained." Blog, Udacity, June 29. Accessed 2022-01-05.

Article Stats

Author-wise Stats for Article Edits

Author
No. of Edits
No. of Chats
DevCoins
29
17
3256
9
23
2833
2354
Words
5
Likes
7135
Hits

Cite As

Devopedia. 2022. "C++ Inheritance." Version 38, January 24. Accessed 2024-06-25. https://devopedia.org/c-plus-plus-inheritance
Contributed by
2 authors


Last updated on
2022-01-24 06:28:49
  • C++ Object Lifecycle
  • C++ Object Initialization
  • C++ Optimization
  • C++ Friend
  • Class Inheritance
  • JavaScript Prototypal Inheritance