Inheritance and Access
reading
Cyganek section 4.3
With inheritance, we create a base class that has member functions that should be generally applicable to any class that we build off of the base. We then create a sub class that inherits the functionality of the base class and can extend it.
Tip
Sometimes inheritance is described as being applicable when there is an is-a relationship.
E.g.:
A dog is an animal
A square is a shape
Access
There are different modes of access that come into play. First within the base class, we expand private and public to include protected:
private : member data and functions is only available to the base class.
protected : member data and functions is available to the base class as well as any class that inherits from it.
public: anyone can access the member data and functions.
If we expect to derive from our class and still want to practice encapsulation, then we would generally make the member data protected.
The second way access comes into play is when we define the class that inherits from
the base. Shortly we’ll look at an example that defines a base class called Quadrilateral
and then a class Rectangle
that inherits from it. We will define the class as:
class Rectangle : public Quadrilateral
The :
says that Rectangle
inherits from Quadrilateral
and
the public
keyword here means that the member data and functions
are inherited with the same access restrictions as in the base class.
We could alternately use protected
or private
here to change
the access to the inherited data.
Constructor
How do we write the constructor of the sub class?
For our Rectangle
example, we might think that we could
just use a member initialization list to initialize the data,
but this does not work.
Note
C++ prevents us from initializing inherited member variables in the member initialization list of the sub-class constructor.
Instead we can explicitly call the constructor of the base class to initialize any data in the base class and then use a member initialization list to initialize anything specific to the sub class.
Now, when we create a Rectangle
, the constructor of the base class
is first executed and then the remainder of the sub class constructor
is executed.
Tip
A nice description of how the constructor of the sub class works is given in the Learn C++ tutorials.
Example
Here’s an example of the base Quadrilateral
class and the
Rectangle
class that inherits from it.
#ifndef SHAPES_H
#define SHAPES_H
#include <vector>
#include <numeric>
class Quadrilateral {
protected:
// store the side data
// order: top, bottom, left, right
std::vector<double> sides;
public:
Quadrilateral()
: sides{1.0, 1.0, 1.0, 1.0} {}
Quadrilateral(double s1, double s2, double s3, double s4)
: sides{s1, s2, s3, s4} {}
double perimeter() {
return std::accumulate(sides.cbegin(), sides.cend(), 0.0);
}
};
class Rectangle : public Quadrilateral {
public:
Rectangle(double width, double height)
: Quadrilateral(width, width, height, height) {}
double area() {
return sides[0] * sides[2];
}
};
#endif
A few notes:
The
Rectangle
constructor takes only 2 arguments, since opposite sides of a rectangle are the same length. We then call the base class constructor to store these in thevector sides
, duplicating the information as needed to initialize all 4 sides.There is no additional member data in
Rectangle
, so no further initialization is needed.The
perimeter
function is general to any quadrilateral, soRectangle
does not need to define it.
Here’s a test driver:
#include <iostream>
#include "shapes.H"
int main() {
Quadrilateral q(1.0, 2.0, 1.0, 2.0);
std::cout << "perimeter of q = " << q.perimeter() << std::endl;
Rectangle r(2.0, 4.0);
std::cout << "perimenter of r = " << r.perimeter() << std::endl;
std::cout << "area of r = " << r.area() << std::endl;
}