Team 2550
Technical Documentation
 All Files Variables Pages
Inheritance & Composition

Inheritance creates an is-a relationship between classes, while composition creates a has-a relationship. In other words, inheritance says, "I am extend class with these other things," while composition says "This other class is one of my components."

Composition

Composition is the term used for placing an object within a class. Say there are two classes, A and B. If you place an object of type A into the declaration of B, that is composition.

//A struct or a class will work here, both are user-defined datatypes.
class A {
private:
int someValue;
int someOtherValue;
public:
A();
A(int a, int b);
int getSomeValue();
int setSomeValue(int val);
int getSomeOtherValue();
int setSomeOtherValue(int val);
};
class B {
private:
int x[10];
public:
B();
A member; //This is composition, placing one user-defined type within another.
};

You can access values in A from an object of B using the dot operator.

B obj; //When the constructor for B is run, it invokes the constructor for A automatically.
obj.member.setSomeValue(7);
...
Note
Although this particular example is useless, there are still times where composition is necessary and useful.

Inheritance

As was said above, inheritance creates an "is-a" relationship between classes.

Inheritance introduces the need for a new encapsulation level: protected. When a member is private, it is not accessible to a class that inherits it. Members that are labeled protected are accessible by derived classes, but not for general use. In other words, protected members are accessible to methods declared within the derived class, but not when the class is instantiated and used.

UML Diagrams

When you are developing an application that uses inheritance, you usually start with a Universal Modeling Language (UML) diagram. A UML diagram shows the relationship between classes within a program. In other words, it shows what classes are components (composition) or others and when classes inherit others.

The UML syntax is slightly different from that of C++ due to the fact that it is designed to describe any programming language.

Note
I used Dia to create the diagrams in this section. It is fairly easy to use, but the terminology is slightly different. Notably, member variables are called "attributes" and methods are called "operations".

Here is a sample UML diagram...

ExampleClass.svg

Explanation:

  • Encapsulation levels are denoted with +, -, or #
    • +: public
    • -: private
    • #: protected
  • Member variables follow a name: type = value syntax. The type and = value parts are optional.
  • Composition is denoted with a diamond pointing at the component type declaration. In this case, it is the Point struct.
  • Member variables are in the top section, and methods are in the bottom. In Dia, you can choose to hide either, as I did in the struct.
  • Methods follow a different syntax: method(arg1:type, arg2:type = value): ReturnType
    • Parameters are optional, and are formatted like members
    • The return type is shown after the name and parameters
  • As you will see below, classes that inherit another class are shown with an arrow pointing to the class they inherit

Example

The best way to show how inheritance works is to use it. For this reason, we will implement a class that contains shape properties, and then use inheritance to create classes that correspond to specific shapes. Many graphics libraries (often used to create games) have something similar to this. Note that this example will require basic trigonometry skills (applications of sine, cosine, and tangent).

ShapeClass.svg
Here is the UML inheritance diagram showing the relation between the Shape class and its decedents and components.
Note
The default constructor for each class is implied.

The Base Class: Shape

Here is the declaration and implementation of the Shape class.

FILE: Shape.hh

#ifndef SHAPE_HH
#define SHAPE_HH
class Point2d {
public:
Point2d();
Point2d(int xValue, int yValue);
int x;
int y;
};
class Shape {
private:
int layer;
Point2d location;
int angleDeg;
protected:
float width;
float height;
public:
Shape();
Shape(float w, float h, Point2d loc, int angle = 0);
//Mutators
void setLayer(int l);
void setWidth(float w);
void setHeight(float h);
void setLocation(Point2d loc);
void chgLocation(Point2d delta);
void setRotation(int degrees);
void chgRotation(int degrees);
//Accessors
int getLayer();
float getWidth();
float getHeight();
Point2d getLocation();
int getRotation();
};
#endif
Note
width and height are protected because other classes may need access to them.

FILE: Shape.cc

#include "Shape.hh"
using namespace std;
//Point2d would more efficient as a struct, but here it is a class
//in order to demonstrate the behavior of constructors when using
//composition.
Point2d::Point2d() {
x = 0;
y = 0;
}
Point2d::Point2d(int xValue, int yValue) {
x = xValue;
y = yValue;
}
//---------------------------------------------------------------------
Shape::Shape() {
layer = 0;
angleDeg = 0;
width = 1;
height = 1;
//The default Point2d constructor is run automatically.
}
Shape::Shape(float w, float h, Point2d loc, int angle) {
layer = 0;
width = w;
height = h;
location = loc;
setRotation(angle);
}
void Shape::setLayer(int l) {
layer = l;
}
void Shape::setWidth(float w) {
width = w;
}
void Shape::setHeight(float h) {
height = h;
}
void Shape::setLocation(Point2d loc) {
location.x = loc.x;
location.y = loc.y;
}
void Shape::chgLocation(Point2d delta) {
location.x += delta.x;
location.y += delta.y;
}
void Shape::setRotation(int degrees) {
angleDeg = degrees % 360;
//An angle modded by 360 makes sure that its value is between 0 and 359
//this means that we don't have to calculate multiple rotations later on.
}
void Shape::chgRotation(int degrees) {
angleDeg += degrees;
angleDeg %= 360;
}
int Shape::getLayer() {
return layer;
}
float Shape::getWidth() {
return width;
}
float Shape::getHeight() {
return height;
}
int Shape::getRotation() {
return angleDeg;
}
Point2d Shape::getLocation() {
return location;
}
Note
When programming, most angles are actually done in radians, which are fractions of a circle in multiples of pi (2*pi = 360 degrees).

Child Class: Rectangle

Here is the declaration and implementation of a very simple child class, Rectangle. The only additional methods it supplies are area() and perimeter().

FILE: Rectangle.hh

#ifndef RECTANGLE_HH
#define RECTANGLE_HH
#include "Shape.hh"
class Rectangle : public Shape {
public:
Rectangle();
Rectangle(float w, float h, Point2d loc, int angle = 0);
int area();
int perimeter();
};
#endif

The encapsulation level before the name of the base class is important. It is the highest level that a member from Shape can be in a Rectangle object. In other words, if public Shape was changed to private Shape, all of the members that were previously public and protected would be private in the child class. Generally speaking, this is not what you want, and if you want to restrict the access to inherited members, composition is typically the way to go. This does not reduce the protection of any members - inheriting a class as public means that previously public members are public, protected members are protected, and private members are private.

FILE: Rectangle.cc

#include "Rectangle.hh"
using namespace std;
//All other methods are defined in Shape. You can call any of Shape's methods
//for a Rectangle object.
Rectangle::Rectangle() {
//The Shape default constructor is automatically run here.
}
Rectangle::Rectangle(float w, float h, Point2d loc, int angle) :
Shape(w, h, loc, angle) //This is an alternative to the default constructor (see below).
{}
int Rectangle::area() {
return width * height;
}
int Rectangle::perimeter() {
return (2 * width) + (2 * height);
}

: Shape(w, h, loc, angle) bypasses the default constructor. In this case, the arguments of the Rectangle constructor are passed to the Shape constructor. This prevents members of the new class from being set twice.

Note
It is also possible to put "using Shape::Shape;" in the class declaration in order to inherit the constructors from Shape, but this is a relatively new feature of C++ and is not supported everywhere. For now, it is best to use the method shown above to pass arguments to non-default constructors.

For demonstration purposes, here is a simple test file.

#include <iostream>
#include "Rectangle.hh"
using namespace std;
int main() {
Rectangle r;
//The following line demonstrates how inheritance works:
//getLocation is a method in Shape, but it is being called from a Rectangle object.
cout << r.getLocation().x << ' ' << r.getLocation().y << '\n';
r.setWidth(3);
r.setHeight(2);
//Call a method that only exists in Rectangle
cout << r.area();
}

Inheritance & Functions

Inheritance also provides a benefit when you are passing a class into a function. You can pass child classes into a function meant for the base class. In other words, you can pass a Rectangle into a function requiring a Shape argument. This makes since, a Rectangle will have no less than a Shape (assuming that it inherited Shape as public).

#include <iostream>
#include "Rectangle.hh"
using namespace std;
void printShape(Shape s);
int main() {
Rectangle r;
printShape(r);
}
void printShape(Shape s) {
cout << "Angle: " << s.getRotation() << " Layer: " << s.getLayer() << '\n'
<< "Location: " << s.getLocation().x << ' ' << s.getLocation().y << '\n'
<< "Width: " << s.getWidth() << " Height: " << s.getHeight() << '\n';
}

Output

Angle: 0 Layer: 0
Location: 0 0
Width: 1 Height: 1    

Note that r is a Rectangle and the function's parameter type is a Shape. This would cause a compiler error if you set the encapsulation level when Rectangle inherited Shape were something other than public.

Note
There is a more efficient way to write this function. See const functions.

Child Class: EquilTriangle

Unlike a rectangle (or most shapes, for that matter), an equilateral triangle has to have the same width and height. This presents a problem when inheriting from the Shape class: the user is able to set the width and height individually. Fortunately, we can redefine member functions within the base class in the child. Let's create an equilateral triangle class, called EquilTriangle, that makes sure the width and height maintain the correct ratio. First, we'll have to use some geometry/trigonometry to determine the ratio between the width and height of an equilateral triangle.

EquilTriangle.svg

The above diagram gives us one very important piece of information: the ratio between the width and height of an equilateral triangle is: 0.866025403784. In other words:

height = 0.866025403784 * width
width = height / 0.866025403784

FILE: EquilTriangle.hh

#ifndef EQUILTRIANGLE_HH
#define EQUILTRIANGLE_HH
#include "Shape.hh"
class EquilTriangle : public Shape {
public:
EquilTriangle();
EquilTriangle(float w, Point2d loc, int angle = 0);
void setWidth(float w);
void setHeight(float h);
float area();
float perimeter();
};
#endif

Note that the above declaration redefines setHeight and setWidth.

FILE: EquilTriangle.cc

#include <cmath>
#include "EquilTriangle.hh"
using namespace std;
const double EquilTriangle_ratio = 0.866025403784;
EquilTriangle::EquilTriangle() {
height = EquilTriangle_ratio * width;
}
EquilTriangle::EquilTriangle(float w, Point2d loc, int angle) :
Shape(w, EquilTriangle_ratio * w, loc, angle)
{
//This constructor is a fairly special case. Because the height depends on
//the width, we can't have the user provide both arguments. The Shape
//constructor has to be run given a width value, and then use the ratio
//we created earlier to determine the height.
}
void EquilTriangle::setWidth(float w) {
width = w;
height = EquilTriangle_ratio * width;
}
void EquilTriangle::setHeight(float h) {
height = h;
width = height / EquilTriangle_ratio;
}
float EquilTriangle::area() {
return (width * height) / 2;
}
float EquilTriangle::perimeter() {
return 3 * width;
}

There is almost nothing new here, so let's run a test program...

#include <iostream>
#include "EquilTriangle.hh"
using namespace std;
void printShape(Shape s);
int main() {
EquilTriangle t(2, Point2d(1, 0), 0);
printShape(t);
t.setWidth(3); //Runs EquilTriangle::setWidth
printShape(t);
t.Shape::setHeight(5); //Runs Shape::setHeight
printShape(t);
}
void printShape(Shape s) {
cout << "Angle: " << s.getRotation() << " Layer: " << s.getLayer() << '\n'
<< "Location: " << s.getLocation().x << ' ' << s.getLocation().y << '\n'
<< "Width: " << s.getWidth() << " Height: " << s.getHeight() << "\n\n";
}

Output

Angle: 0 Layer: 0
Location: 1 0
Width: 2 Height: 1.73205

Angle: 0 Layer: 0
Location: 1 0
Width: 3 Height: 2.59808

Angle: 0 Layer: 0
Location: 1 0
Width: 3 Height: 5

As you can see, the program worked. However, running Shape::setHeight defeats the purpose of this class, to keep the width and height at a constant ratio. This is not always the case, and there are situations where accessing the original function (not the redefined version) can be useful.

Virtual Functions

It is best to put as many methods as possible in the base class. That way, functions that accept the base class are capable of doing more. For instance, the area and perimeter methods are in every child class of Shape that we declared, and it is safe to say that every shape has both an area and perimeter (circumference in the case of circles, but it is the same thing). The problem we encounter is that the means by which you calculate those values differ from shape to shape. Because of this, it doesn't make since to define the methods in the base class. This is where virtual methods become useful. Basically, they make the base class incomplete. You can still call a virtual function, but unless the user (programmer) inherits the class and defines those functions, the class cannot be used. Let's take another look at the printShape function.

void printShape(Shape s) {
//All of the methods that are called here exist in the base class.
cout << "Angle: " << s.getRotation() << " Layer: " << s.getLayer() << '\n'
<< "Location: " << s.getLocation().x << ' ' << s.getLocation().y << '\n'
<< "Width: " << s.getWidth() << " Height: " << s.getHeight() << '\n';
}

There is no way to call the area and perimeter methods in the above function because they don't exist in the Shape class. The problem is that the base class cannot define them, and having them return a default value, such as 0, makes no since. In addition, the Shape class does not implement a specific shape. It is like a rectangle, but it does not define a rectangle any more than it defines any other shape. Let's modify the UML diagram we create earlier in order to deal with this shortcoming.

ShapeClassVirtual.svg
The area and perimeter methods in the UML diagram above are italicized. This denotes that they are virtual functions.

The declaration of Shape will now look like this...

FILE: Shape.hh

#ifndef SHAPE_HH
#define SHAPE_HH
class Point2d {
public:
Point2d();
Point2d(int xValue, int yValue);
int x;
int y;
};
class Shape {
private:
int layer;
Point2d location;
int angleDeg;
protected:
float width;
float height;
public:
Shape();
Shape(float w, float h, Point2d loc, int angle);
//Mutators
void setLayer(int l);
void setWidth(float w);
void setHeight(float h);
void setLocation(Point2d loc);
void chgLocation(Point2d delta);
void setRotation(int degrees);
void chgRotation(int degrees);
//Accessors
int getLayer();
float getWidth();
float getHeight();
Point2d getLocation();
int getRotation();
//Utility
virtual float area() = 0;
virtual float perimeter() = 0;
};
#endif
Note
The two virtual functions are at the bottom. The = 0 is critical because it makes them pure virtual functions. In other words, it makes the class abstract, meaning that you can't create a Shape object. In order to use Shape, you have to inherit it and define the virtual functions. Normal virtual functions behave similar to method overriding (see EquilTriangle).

The implementation of Shape does not change.

Child Class: Ellipse

In order to demonstrate how (pure) virtual functions work, here is another child class: Ellipse (which can also be used for circles).

FILE: Ellipse.hh

#ifndef ELLIPSE_HH
#define ELLIPSE_HH
#include "Shape.hh"
class Ellipse : public Shape {
public:
bool isCircle();
float area(); //This and the prototype after define the virtual functions, making the class non-abstract.
float perimeter();
};
#endif

FILE: Ellipse.cc

#include <cmath>
#include "Ellipse.hh"
using namespace std;
bool Ellipse::isCircle() {
return (width == height) ? true : false;
}
float Ellipse::area() {
//M_PI is defined in cmath and approximates the value of pi (3.1415926...).
return M_PI * (width/2) * (height/2);
}
//The perimeter of an ellipse is difficult to calculate exactly (it involves calculus), so this implementation approximates it.
float Ellipse::perimeter() {
//Equation from http://www.mathsisfun.com/geometry/ellipse-perimeter.html
float a, b;
double h;
if (width > height)
{
a = width / 2;
b = height / 2;
}
else
{
a = height / 2;
b = width / 2;
}
h = pow(a - b, 2) / pow(a + b, 2);
return M_PI * (3*(a+b) - sqrt((3*a + b)*(a + 3*b)));
}

Here is an example main() using Ellipse...

Sample main()

#include <iostream>
#include "Ellipse.hh"
using namespace std;
void print(Shape& s);
int main() {
Ellipse e;
e.setWidth(2);
print(e);
}
void print(Shape& s) { //Passing by reference is necessary for abstract classes. Later we will learn how to work with const classes.
cout << "Angle: " << s.getRotation() << " Layer: " << s.getLayer() << '\n'
<< "Location: " << s.getLocation().x << ' ' << s.getLocation().y << '\n'
<< "Width: " << s.getWidth() << " Height: " << s.getHeight() << '\n'
<< "Area: " << s.area() << " Perimeter: " << s.perimeter();
}

Output

Angle: 0 Layer: 0
Location: 0 0
Width: 2 Height: 1
Area: 1.5708 Perimeter: 4.84421

This is different from the override in EquilTriangle in that you cannot access Shape::area(). If you change the print function so that it tries to call Shape::area or Shape::perimeter, you will get a compiler error.

void print(Shape& s) {
cout << "Angle: " << s.getRotation() << " Layer: " << s.getLayer() << '\n'
<< "Location: " << s.getLocation().x << ' ' << s.getLocation().y << '\n'
<< "Width: " << s.getWidth() << " Height: " << s.getHeight() << '\n'
<< "Area: " << s.Shape::area() << " Perimeter: " << s.perimeter();
}

Compiler Output

In function `print(Shape&)':
test.cc:(.text+0x77): undefined reference to `Shape::area()'
collect2: error: ld returned 1 exit status

Compilation exited abnormally with code 1
Note
This compiler output is a sample. Your compiler will likely not give the same result, but the message will be similar.

Multiple Inheritance

Although it has limited uses, multiple inheritance can sometimes be helpful. Up until now, any child class inherited a single base class, but you can actually have any number of base classes.

Example:

Let's say there is a class called Render that can display an object on the screen. The members of a Render object do not matter here, so I will not provide a declaration. Let's say we want to make a rectangle class that is renderable.

class Rectangle : public Shape, public Render {
//Declaration goes here.
}

There is no real difference between multiple and single inheritance except that there is not a single base class. The members of both Shape and Render are callable in the child, and functions that accept either Shape or Render objects can also operate on the newRectangle` class.

Note
The reason I did not create a more detailed example for Render is because it requires a variable number of points to be stored, which requires the use of dynamic memory - I have not covered this yet and therefore do not feel comfortable introducing unexplained syntax.