Last Updated:

Inheritance in C++ 

Inheritance in C++

1. Introduction

Classes are used to model concepts of the real and programmatic world. However, no concept exists in isolation. It coexists with related concepts and it is to this connection that it owes its power. The concept of a derived class and its associated mechanisms of language are intended to express hierarchical relations, i.e., to reflect the commonality of classes. For example, the concepts of a circle and a triangle are related to the fact that both are figures. The concept of the figure is common to them. Therefore, we must explicitly define that the Circle and Triangle classes share a common Base Shape class. Presenting the concepts of "circle" and "triangle" in the program without introducing the concept of "figure" would mean the loss of something significant.

2. Base and derived classes

Consider the example given in the introduction. Both the circle and the triangle are shapes, but if you do not use special tools to explicitly indicate this fact, the compiler itself will not be able to draw such a conclusion. Accordingly, we will not be able, for example, to put pointers to objects of the Circle and Triangle classes in the same list. This relationship between classes is called inheritance.

A class can be derived from another class called the base class of a derived class. A class can be derived from one or more classes. The generated classes inherit the properties of the base classes, including member data and member functions. In addition, additional member data and member functions can be declared in a derived class.

Declaring a derived class has the following syntax:
class <name of the derived class> : <Access specifier> <base class name> [, <Access specification> <base class name> ] { ... };

The inheritance relationship between classes can be represented graphically.

inheritance in c++

A popular and effective implementation of the concept of derived classes is to represent the object of a derived class as a base class object and information related only to the derived class.

inheritance in c++

A derived class can itself serve as a base class.

If the members of the base class are not overridden in the derived class, they are denoted and treated in the same way as the members of the derived class. Members of a base class are said to be inherited by a derived class. The scope resolution :: operation can be used to explicitly reference a member of a base class. This provides access to the name, which is overridden in the derived class.

Returning to the example in question, we see that the circle is a figure. Therefore, Circle* can be used as Shape*. However, a shape is not necessarily a circle, so Shape* cannot be used as a Circle*. In general, a pointer to a derived class can be implicitly converted to a pointer to a uniquely accessible base class. A reference to a derived class can be implicitly converted to a reference to a uniquely accessible base class.

class Base
 { public:
    int a, b;
  };

class Derived : public Base
 { public:
    int b, c;
  };

Derived d;      
d.a       = 1;
d.Base::b = 2;
d.b       = 3;
d.c       = 4;
Base *bp  = &d;

A class is called a direct base class if it is referenced when declaring a derived class, and an indirect base class if it is the base class for one of the base classes of the declared class.


class A { public:  void f(); };
class B : public A { };
class C : public B
 { public:
    void f(); void ff();
  };

In the <classname>::<name> the class name can be the name of the indirect base class, this class name identifies the class in which the name lookup begins.

void C::ff()
 { f();
   A::f();
   B::f();
  }

2.1. Access Specifiers for Base Classes

The access specifier can be:

  • public – in this case, the public members of the base class become public members of the derived class, and the protected members of the base class become the protected members of the derived class;
  • protected – in this case, the public and protected members of the base class become protected members of the derived class;
  • private—In this case, the public and protected members of the base class become private members of the derived class.

Private class members are not available even in a derived class, unless it is declared friendly.

By default, the private access specifier is assumed.

class Base
 { private:
    int a;
   protected:
    int b;
   public:
    int c;
  }; 
       
class Derived1 : public Base
 {
   ...
  }; 
      
// a unavailable // b – protected member of class Derived1 // c – public member of class Derived1 
class Derived2 : protected Base
 {
   ...
  }; 
      
// a unavailable // b and c are protected members of the Derived2 class 
class Derived3 : private Base
 {
   ...
  }; 

2.2. Access Announcements

You can adjust access to a base class member in a derived class by mentioning its qualified name* in the derived class (with or without the using keyword). This mention is called an access declaration.

class Base
 { public:
    int n;
   ...
  };

class Derived : private Base
 { public:      
    Base::n;
   ...
  };

Private members of the base class remain inaccessible anyway. Access to protected and public class members can be adjusted in any direction.

The access declaration for the name of a shared function establishes access to all functions with that name in the base class.

class Base
 { public:
    void f();
    void f(int n);
  };

class Derived : private Base
 { public:      
    Base::f;
  };

Access to a member of a base class cannot be adjusted in a derived class if it defines a member with the same name at the same time.

class Base
 { public:
    void f();
  };

class Derived :  private Base
 { public:
    void f(int n);      
    Base::f;
  } 

2.3. Constructors and destructors

Derived classes may also need constructors. Constructors are not inherited, they must be defined in the class itself. If the base class has a constructor, it must be called. Default constructors can be invoked implicitly. However, if all the constructors of a base class require arguments, the constructor of that base class must be called explicitly. The base class constructor arguments are specified in the derived class constructor definition.

class Base
 { private:
    int a;
   protected:
    int b;
   public:
    Base(int aa, int bb) : a(aa), b(bb) { }
  };

class Derived : public Base
 { private:
    int c;
   public:
    Derived(int aa, int bb, int cc) :      
      Base(aa, bb),
      c(cc)
     { }
  }; 

A derived class constructor can specify initializers for its own members and base class members, but it cannot directly initialize base class members.

class Derived : public Base
 { private:
    int c;
   public:
    Derived(int aa, int bb, int cc) :      
      a(aa), b(bb),
      c(cc)
     { }
  }; 

Class objects are created from the bottom up: first the base class, then the derived class. They are destroyed in the opposite order. Base class subobjects are constructed in the order in which they are declared in the class and destroyed in reverse order.

2.4. Accessing Protected Class Members

A friendly function or member function of a derived class can have access to a secure static member of the base class. A friendly function or a member function of a derived class can access a protected non-static member of one of its base classes only by pointer or reference to a derived class, or through an object of a derived class.

class Base
  {protected:
     intn;
   };

class Derived1 : public Base { ... };

class Derived2 : public Base
  { void member_function(Base *pb, Derived1 *p1);
    friend void friend_function(Base *pb, Derived1 *p1, Derived2 *p2);
   };
  
void Derived2::member_function(Base *pb, Derived1 *p1)
  {pb->n = 0;
    p1->n = 0;
    n = 0;
   }
  
// Prohibited! Prohibited! Accessing through an Object of the Derived2 Class
void friend_function(Base *pb, Derived1 *p1, Derived2 *p2)
  {pb->n = 0;
    p1->n = 0;
    p2->n = 0;
   }

2.5. Using Protected Class Members

 

The simple "open/private" data hiding model works well for specific types. However, when using derived classes, there are two kinds of class users: derived classes and all other functions. The open/closed model allows the programmer to distinguish between developers and everyone else, but it does not provide special maintenance for derived classes.

The C++ language allows you to flexibly control access to class members by using three access specifiers. However, protected class members are more susceptible to abuse than private members. Placing a significant portion of the data in a common class that is accessible to all derived classes risks destroying that data. Moreover, just like open, secure data, it is not easy to restructure because of the difficulty of finding all the cases of its use. Thus, secure data leads to maintenance problems.

Fortunately, there is no need to use secure data. Class members are closed by default and are generally the best option. When developing a derived class, you can use the base class interface to work with private members of the base class.

Note that all of these objections don't matter much to protected features. Security is a great way to specify operations for use in derived classes.

3. Example

 

We implement the example used in the lecture – the Shapes base class and the Circle and Triangle classes derived from it.

Shapes.h file

 

#define SHAPES // Guardian of inclusion

class Shapes
 {protected:
    static int count;
    intcolor;
    int iam; // Field for setting the object type
    int left, top, right, bottom;
    Shapes() { count++; } // Protected constructor
   public:
    enum{CIRCLE, TRIANGLE};
    enum {LEFT, UP, RIGHT, DOWN};
    ~Shapes() { count--; }
    static int GetCount() { return count; }

    // Access to protected members of the base class is only allowed through objects or pointers to objects of the derived class.
    // When in the function void Move(int where, const Shapes *shape) we refer to the previous shape just as a shape,
    // we won't be able to access these class members. Therefore, functions are needed that return one value or another.
    // The functions defined in the class are inline, so there is no overhead to call them.
    int Left() const { return left; }
    int Top() const { return top; }
    int Right() const { return right; }
    int Bottom() const { return bottom; }
    int I_am() const { return iam; }
  };

The Shapes file.cpp

 

#include "Shapes.h"

int Shapes::count = 0;

Circle.h file

 

#if !defined(SHAPES) // Checking the include guard
  #include "Shapes.h"
#endif

class Circle : public Shapes
  {private:
     int cx, cy, radius;
    public:
     Circle(int x = 0, int y = 0, int r = 0, int c = 0);
     ~Circle() { }
     voidDraw();
     void Move(int where, const Shapes *shape);
   };

The Circle file.cpp

 

#include "Circle.h"

Circle::Circle(int x, int y, int r, int c)
 { cx = x; cy = y; radius = r;
   color=c;
   left = cx - radius; top = cy - radius;
   right=cx+radius; bottom = cy + radius;
   iam=CIRCLE; // Remember that the object is a circle
  }

void Circle::Draw()
 { ... }

void Circle::Move(int where, const Shapes *shape)
 {
   switch (where)
    {case LEFT:
       cx = shape->Left() - radius;
       cy = (shape->Top() + shape->Bottom()) / 2;
       break;
      case UP:
       cx = (shape->Left() + shape->Right()) / 2;
       cy = shape->Top() - radius;
       break;
      case RIGHT:
       cx = shape->Right() + radius;
       cy = (shape->Top() + shape->Bottom()) / 2;
       break;
      case DOWN:
       cx = (shape->Left() + shape->Right()) / 2;
       cy = shape->Bottom() + radius;
       break;
     }
   left = cx - radius; top = cy - radius;
   right=cx+radius; bottom = cy + radius;
  }

Triangle.h file

 

#if !defined(SHAPES) // Checking the include guard
  #include "Shapes.h"
#endif

class Triangle : public Shapes
  {private:
     int x1, y1, x2, y2, x3, y3;
    public:
     Triangle(int x1 = 0, int y1 = 0, int x2 = 0, int y2 = 0, int x3 = 0, int y3 = 0, int c = 0);
     ~Triangle() { }
     voidDraw();
     void Move(int where, const Shapes *shape);
   };

Triangle file.cpp

 

#include "Triangle.h"

int Max(int ​​a, int b, int c);
int Min(int a, int b, int c);

Triangle::Triangle(int x1, int y1, int x2, int y2, int x3, int y3, int c)
 { this->x1 = x1;
   this->y1 = y1;
   this->x2 = x2;
   this->y2 = y2;
   this->x3 = x3;
   this->y3 = y3;
   color=c;
   left = Min(x1, x2, x3);
   top = Min(y1, y2, y3);
   right = Max(x1, x2, x3);
   bottom = Max(y1, y2, y3);
   iam = TRIANGLE; // Remember that the object is a triangle
  }

void Triangle::Draw()
 { ... }

void Triangle::Move(int where, const Shapes *shape)
 { int dx, dy;

   switch (where)
    {case LEFT:
       dx = shape->Left() - right;
       dy = (shape->Top() - top + shape->Bottom() - bottom) / 2;
       break;
      case UP:
       dx = (shape->Left() - left + shape->Right() - right) / 2;
       dy = shape->Top() - bottom;
       break;
      case RIGHT:
       dx = shape->Right() - left;
       dy = (shape->Top() - top + shape->Bottom() - bottom) / 2;
       break;
      case DOWN:
       dx = (shape->Left() - left + shape->Right() - right) / 2;
       dy = shape->Bottom() -top;
       break;
     }
   x1 +=dx; y1 += dy;
   x2 +=dx; y2 += dy;
   x3 +=dx; y3 += dy;
   left = Min(x1, x2, x3);
   top = Min(y1, y2, y3);
   right = Max(x1, x2, x3);
   bottom = Max(y1, y2, y3);
  }

The main file.cpp

 

#include "Circle.h"
#include "Triangle.h"

void main()
 { Shapes* shapes[10]; // Because the constructor for the Shapes class is protected,
                                                                      // you can declare an array of pointers, but not an array of shapes
   shapes[0] = new Circle(100, 100, 30, 50);
   shapes[1] = new Triangle(0, 0, 20, 0, 0, 20, 90);
   shapes[2] = new Circle(200, 200, 50, 20);
   
   for(int i = 0; i < Shapes::GetCount(); i++)
    if (shapes[i]->I_am() == Shapes::CIRCLE) // Check Object Type
     static_cast<Circle*>(shapes[i])->Draw(); // Requires a transformation to call the correct function
    else
     static_cast<Triangle*>(shapes[i])->Draw();
   
   for(int i = 1; i < Shapes::GetCount(); i++)
    if (shapes[i]->I_am() == Shapes::CIRCLE)
     static_cast<Circle*>(shapes[i])->Move(Shapes::LEFT, shapes[i - 1]);
    else
     static_cast<Triangle*>(shapes[i])->Move(Shapes::LEFT, shapes[i - 1]);
   
   for(int i = 0; i < Shapes::GetCount(); i++)
    if (shapes[i]->I_am() == Shapes::CIRCLE)
     static_cast<Circle*>(shapes[i])->Draw();
    else
     static_cast<Triangle*>(shapes[i])->Draw();
   
   for(int i = 0, n = Shapes::GetCount(); i < n; i++)
    if (shapes[i]->I_am() == Shapes::CIRCLE)
     delete static_cast<Circle*>(shapes[i]);
    else
     delete static_cast<Triangle*>(shapes[i]);
  }