Lesson 2 - Class Inheritance
by Zoran Horvat @zoranh75
In previous lesson we have defined a structure called Shape to accommodate location and name of the geometric figure. But with that
structure we cannot precisely describe any particular shape, like ellipse or
rectangle. More fields are required to meet the needs. But these fields come in
different packages when describing specific shapes. Ellipse requires two radius
values, rectangle requires width and height. Some other shape would be defined
with other values. To fully define each of the shapes means to step into its
specific features, and those are ones not shared with other shapes.
Now we are standing at the point where definitions of desired structures start
to disperse. One line of development would lead to a ellipse; another line to a
rectangle, and so on. But still, ellipse and rectangle share their location and
name definition as provided by the Shape structure. This problem is solved
using the concept of subtyping. We will introduce other structures to extend
the Shape structure and to accommodate more specific features of particular geometric
shapes. But here comes the beautiful part, which is probably best described by
the code. Namely, both Ellipse and Rectangle will now contain Shape structure
as their internal part:
struct Shape _base;
struct Shape _base;
The following picture shows memory layout of our new structures. Now it becomes
clear why Shape structure had to be instantiated within each of the instances: now Ellipse and Rectangle both contain fields that are common to all shapes - location and name.
This process of extending one type to accommodate additional values is referred
to as subtyping. It is said that Ellipse is a subtype of Shape. Rectangle is another subtype of Shape. Shape is called the supertype or base type of Ellipe and of Rectangle. For this reason, Shape-typed field in Ellipse and Rectangle structures was named _base (underscore was added to avoid mixing it up with base keyword, which is used in some object-oriented languages like C# to denote
Constructors and Destructors in Subtypes
When Shape structure was designed we have coded two constructors and a destructor. These
functions had special purpose to initialize and release instances. Now we can
ask a question: How should constructors and destructors operate on subtypes?
Subtypes rely on their supertypes and so should it be with their constructors
and destructors. Supertype defines a subset of values contained in the derived
type. Supertype's constructor operates only on those supertype-provided values
in order to initialize them for use. Subtype's constructor requires base part
of the record already initialized, so we will enforce base type's constructor
to be called first. Conversely, releasing resources added by the subtype may
require base part of the record still operational. Therefore, subtype's
destructor will fully execute and only then base type's destructor will be
called to finish the job.
We are now ready to define the Ellipse structure and accompanying functions.
struct Shape _base;
void Ellipse_Constructor(struct Ellipse *_this);
void Ellipse_Constructor1(struct Ellipse *_this,
float locationX, float locationY, float radiusX, floatradiusY);
void Ellipse_Destructor(struct Ellipse *_this);
void Ellipse_Constructor(struct Ellipse *_this)
_this->radiusX = 0.0F;
_this->radiusY = 0.0F;
void Ellipse_Constructor1(struct Ellipse *_this, float locationX, float locationY, float radiusX, float radiusY)
Shape_Constructor1((struct Shape*)_this, locationX, locationY);
_this->radiusX = radiusX;
_this->radiusY = radiusY;
void Ellipse_Destructor(struct Ellipse *_this)
Observe implementation of the first constructor. It calls base type's
constructor and simply passes this pointer to it. This may look awkward but
recall from the Ellipse structure declaration that Shape structure was instantiated as the first element within the Ellipse structure. This means that pointer to Ellipse is the same as pointer to its _base member, which is actually a Shape structure instance. After ellipse's constructor has called base type's
constructor, it simply initializes remainder of the Ellipse record, which boils down to setting the radiuses to neutral value.
Destructor is the simplest part of the ellipse type and it is so because Ellipse does not add any dynamically allocated memory that should be released by the
destructor. Therefore, ellipse's destructor simply relegates the call to base
type's destructor to do all the work.
Rectangle structure is defined in basically the same way. We will provide here only the
struct Shape _base;
void Rectangle_Constructor(struct Rectangle *_this);
void Rectangle_Constructor1(struct Rectangle *_this, float locationX, float locationY,
float width, float height);
void Rectangle_Destructor(struct Rectangle *_this);
Below is the listing of main.c file which initializes one ellipse and one rectangle and then modifies their
int main(char args)
struct Ellipse *ellipse = NULL;
struct Rectangle *rectangle = NULL;
ellipse = (struct Ellipse*)malloc(sizeof(struct Ellipse));
Ellipse_Constructor1(ellipse, 1.0F, 2.0F, 3.0F, 4.0F);
Shape_SetName((struct Shape*)ellipse, "Ellipse");
rectangle = (struct Rectangle*)malloc(sizeof(struct Rectangle));
Rectangle_Constructor1(rectangle, 3.0F, 4.0F, 5.0F, 6.0F);
Shape_SetName((struct Shape*)rectangle, "Rectangle");
Shape_SetLocation((struct Shape*)ellipse, 5.0F, 6.0F);
Shape_SetLocation((struct Shape*)rectangle, 7.0F, 8.0F);
Here is the program output:
Ellipse's location is (1.00, 2.00).
Rectangle's location is (3.00, 4.00).
Ellipse's location is (5.00, 6.00).
Rectangle's location is (7.00, 8.00).
It is obvious that program is working correctly, at least when talking about
shape locations. Ellipse and rectangle are initialized at locations (1, 2) and
(3, 4), but after two calls made to Shape_SetLocation function, they have moved
to (5, 6) and (7, 8), respectively. Moreover, when calling Shape_PrintOut and
Shape_SetLocation functions which both accept pointer to Shape as an argument, we are explicitly casting pointer type to Shape, like in this
By doing so, we have applied the principle called pointer type substitution.
Pointer to subtype - Ellipse - is passed into function argument which is defined as pointer to
corresponding supertype - Shape. So much about syntax. But to explain how does this code really work, we will
have to take a look at the memory layout once again. Picture below shows a
pointer named _this, which is an argument of a function receiving Shape (e.g. Shape_SetLocation function). Caller decides to pass a pointer to Rectangle structure instead. However, function knows only about "general" shape, with
only name and location. Same case is when pointer to Ellipse is passed - function is oblivious of the radiusX and radiusY fields. Net result is that Shape_SetLocation function operates only on name, locationX and locationY fields, ignoring the presence of any other field, if there.
Now it is obvious why Shape structure was defined as first field in the Ellipse and Rectangle structures. In order to pass pointer to subtype when pointer to supertype is
expected, supertype-defined content must occupy beginning of the overall
Implementation in Classes
In previous sections we have thoroughly discussed object-oriented design of two
structure types derived from Shape. In this section we will simply rewrite them into C++ classes: Ellipse and Rectangle, both subclasses of the Shape class.
First listing will present declaration of the Ellipse class, contained in the ellipse.hpp header file. As already seen when Shape class was designed, class declaration is stunningly similar to structure and
accompanying functions declaration.
class Ellipse: public Shape
Ellipse(float locationX, float locationY, float radiusX, floatradiusY);
Class definition is provided in ellipse.cpp file.
Ellipse::Ellipse() // Implicitly calls Shape() parameterless constructor
Ellipse::Ellipse(float locationX, float locationY, float radiusX, float radiusY): Shape(locationX, locationY)
this->radiusX = radiusX;
this->radiusY = radiusY;
This piece of code raises some questions, syntactical rather than substantial.
Parameterless constructor does not call parameterless Shape constructor explicitly. By convention, C++ invokes default (parameterless)
constructor of the base class every time when not stated otherwise. This
"otherwise" case is demonstrated by the second constructor, which explicitly
calls Shape's constructor with two float arguments to initialize ellipse’s location to specific point. Also, we didn't
have to specify the destructor because it would be empty - C++ compiler will
add it for us.
Rectangle class is defined almost the same as the Ellipse class and we will leave its implementation to the reader to exercise. And here
is the main function which utilizes these two new classes.
using namespace std;
Ellipse *ellipse = new Ellipse(1.0F, 2.0F, 3.0F, 4.0F);
Rectangle *rectangle = new Rectangle(2.0F, 3.0F, 4.0F, 5.0F);
When this code is executed, output produced looks same as ever:
Ellipse's location is (1.00, 2.00)
Rectangle's location is (2.00, 3.00)
Observe how SetName and PrintOut methods have been called directly on objects of Ellipse and Rectangle classes. No casting was required to obtain pointer to base class. This is
fundamental principle in object-oriented languages. Methods defined in the base
class are present in the derived class as well. It is said that derived classes
have inherited these methods from the base class.
Published: Jul 14, 2012; Modified: Jul 17, 2012
Zoran is software architect dedicated to clean design and CTO in a growing software company. Since 2014 Zoran is an author at Pluralsight where he is preparing a series of courses on design patterns, writing unit and integration tests and applying methods to improve code design and long-term maintainability.
Follow him on Twitter @zoranh75 to receive updates and links to new articles.
Watch Zoran's video courses at pluralsight.com (requires registration):
Tactical Design Patterns in .NET: Managing Responsibilities
Applying a design pattern to a real-world problem is not as straightforward as literature implicitly tells us. It is a more engaged process. This course gives an insight into tactical decisions we need to make when applying design patterns that have to do with separating and implementing class responsibilities. More... p>
Tactical Design Patterns in .NET: Control Flow
Improve your skills in writing simpler and safer code by applying coding practices and design patterns that are affecting control flow. More...
Improving Testability Through Design
This course tackles the issues of designing a complex application so that it can be covered with high quality tests. More...
Share this article