C++ Tidbits – Polymorphism

Good morning. It is a relatively cold Saturday in the Twin Cities. For some reason, this winter season has been warmer than usual with very little snow coverage. For example, today the high temperature will be around the freezing point. Some people (like me) like it while others hate it. There is no way to please everyone at once.

In an attempt to review in a systematic way some features of C++ I will be experimenting with polymorphism. In programming languages and type theory, polymorphism is the provision of a single interface to entities of different types or the use of a single symbol to represent multiple different types. To read more about it you may refer here.

Let’s start by defining an Employee and a Manager classes. When we call their respective gross pay methods they both work as expected. We then define a vector of Employee type and place in it the two objects we just created. The console output for our description follows:

emp1 gross pay: 1000
mgr1 gross pay: 1200

emps[0]: 1000
emps[1]: 48000

The code associated with our output and description follows:

// **** with a vector (does not seem to work) ****
vector<Employee> emps;

Employee emp1("Mike", "Jones", 25.00);
Manager mgr1("Peter", "Smith", 1200.00, true);

cout << "emp1 gross pay: " << emp1.grossPay(40) << endl;
cout << "mgr1 gross pay: " << mgr1.grossPay(40) << endl;
cout << endl;

emps.push_back(emp1);
emps.push_back(mgr1);

for (int i = 0; i < emps.size(); i++)
    cout << "emps[" << i << "]: " << emps[i].grossPay(40) << endl;
cout << endl;

Given that the type of vector is Employee, the compiler called the Employee method for both the Employee and the Manager objects. As we saw in the previous post, Employee is the base class and Manager is the derived class.

If we update the grossPay() method in the Employee.h class definition as follows:

#pragma once

using namespace std;

#include <iostream>
#include <string>

// **** our base class ****
class Employee 
{

//private:
protected:
	int		employeeID;
	string	firstName;
	string	middleName;
	string	lastName;

	double	payRate;

public:

	// **** constructors ****
	Employee();

	Employee(string firstName, string lastName);

	Employee(string firstName, string lastName, double payRate);

	// **** destructor ****
	~Employee();

	// **** getters and setters ****
	string getFirstName();
	void setFirstName(string firstName);

	string getLastName();
	void setLastName(string lastName);

	double getPayRate();
	void setPayRate(double payRate);

	int getEmployeeID();
	void setEmployeeID(int employeeID);

	// **** to string ****
	string toString();
	string toString2();

	// **** other methods ****
	virtual double grossPay(int hours);
};

In the Employee base class we have defined:

#include "pch.h"

#include <sstream>

// **** constructor ****
Employee::Employee()
{
	this->firstName		= "";
	this->middleName	= "";
	this->lastName		= "";

	this->employeeID	= 0;
	this->payRate		= 0.0;
}

// **** constructor ****
Employee::Employee(string firstName, string lastName)
{
	this->firstName		= firstName;
	this->middleName	= "";
	this->lastName		= lastName;

	this->employeeID	= 0;
	this->payRate		= 0.0;

}

// **** constructor ****
Employee::Employee(string firstName, string lastName, double payRate)
{
	this->firstName		= firstName;
	this->middleName	= "";
	this->lastName		= lastName;

	this->employeeID	= 0;
	this->payRate		= payRate;
}

// **** destructor ****
Employee::~Employee()
{
	// **** to do: free allocated resources **** 
}

// **** getters and setters ****
string Employee::getFirstName()
{
	return this->firstName;
}

void Employee::setFirstName(string firstName)
{
	this->firstName = firstName;
}

string Employee::getLastName()
{
	return this->lastName;
}

void Employee::setLastName(string lastName)
{
	this->lastName = lastName;
}

double Employee::getPayRate()
{
	return this->payRate;
}

void Employee::setPayRate(double payRate)
{
	this->payRate = payRate;
}

int Employee::getEmployeeID()
{
	return this->employeeID;
}

void Employee::setEmployeeID(int employeeID)
{
	this->employeeID = employeeID;
}

// **** to string ****
string Employee::toString()
{
	return "firstName:" + firstName + " lastName: " + lastName + " employeeID: " + to_string(employeeID) + " payRate: " + to_string(payRate);
}

string Employee::toString2()
{
	stringstream stm;
	stm << "firstName:" << firstName << " lastName: " << lastName << " employeeID: " << employeeID << " payRate: " << payRate; return stm.str(); } // **** other methods **** double Employee::grossPay(int hours) { return this->payRate * hours;
}

In the Manager derived class we have flagged the same method as virtual as illustrated as follows:

#pragma once

using namespace std;

#include <iostream>
#include <string>

// **** our derived class ****
class Manager : public Employee
{

private:
	bool isSalaried;

public:

	// **** constructor(s) ***
	Manager();

	Manager(string firstName, string lastName, double payRate, bool isSalaried);

	// **** destructor ****
	~Manager();

	// **** getter and setters ****
	bool getIsSalaried();
	void setIsSalaried(bool isSalaried);

	// **** other methods ****
	double grossPay();
	virtual double grossPay(int hours);

	// **** to string ****
	string toString();
};

The code for the Manager class follows:

#include "pch.h"

// **** default constructor ****
Manager::Manager() : isSalaried(true)
{
}

// **** constructor ****
Manager::Manager(string firstName, string lastName, double payRate, bool isSalaried)
	: Employee(firstName, lastName, payRate)			// call to base classed constructor
{
	this->isSalaried = isSalaried;
}

// **** destructor ****
Manager::~Manager()
{
	// **** to do: free allocated resources **** 
}

// **** getters and setters ****
bool Manager::getIsSalaried()
{
	return this->isSalaried;
}

void Manager::setIsSalaried(bool isSalaried)
{
	this->isSalaried = isSalaried;
}

// **** other methods ****
double Manager::grossPay()
{
	return this->payRate;
}

double Manager::grossPay(int hours)
{
	if (isSalaried)
		return this->payRate;
	else
		return this->payRate * hours;
}

// **** to string ****
string Manager::toString()
{
	return "firstName: " + firstName + " lastName: " + lastName + " employeeID: " + to_string(employeeID) + 
			" payRate: " + to_string(payRate) + " isSalaried: " + (isSalaried ? "salaried" : "hourly");
}

Now we will update the test code to include a vector using Employee class objects and a second one using pointers to Employee class objects.

	// **** with a vector (does not work) ****
	vector<Employee> emps;

	Employee emp1("Mike", "Jones", 25.00);
	Manager mgr1("Peter", "Smith", 1200.00, true);

	cout << "emp1 gross pay: " << emp1.grossPay(40) << endl;
	cout << "mgr1 gross pay: " << mgr1.grossPay(40) << endl;
	cout << endl;

	emps.push_back(emp1);
	emps.push_back(mgr1);

	for (int i = 0; i < emps.size(); i++)
		cout << "emps[" << i << "]: " << emps[i].grossPay(40) << endl;
	cout << endl;

	// **** with a pointer (works) ****
	Employee *empPtr;

	empPtr = &emp1;
	cout << "emp1 gross pay: " << empPtr->grossPay(40) << endl;

	empPtr = &mgr1;
	cout << "mgr1 gross pay: " << empPtr->grossPay(40) << endl;
	cout << endl;

	// **** with a vector of Employee pointers ****
	vector<Employee *> emps1;

	emps1.push_back(&emp1);
	emps1.push_back(&mgr1);

	for (int i = 0; i < emps1.size(); i++)
		cout << "emps1[" << i << "]: " << emps1[i]->grossPay(40) << endl;
	cout << endl;

The results from the console follow:

emp1 gross pay: 1000
mgr1 gross pay: 1200

emps[0]: 1000
emps[1]: 48000

emp1 gross pay: 1000
mgr1 gross pay: 1200

emps1[0]: 1000
emps1[1]: 1200

As you can see, even with the virtual declaration the implementation with the first vector does not work, but when using pointers it does.

An abstract type is a type in a nominative type system that cannot be instantiated directly; a type that is not abstract – which can be instantiated – is called a concrete type. Every instance of an abstract type is an instance of some concrete subtype. If interested in more information you could click here.

Let’s create an abstract class Shape from which we could derive multiple classes. The definition for this abstract class follows:

#pragma once

using namespace std;

#include <iostream>
#include <string>

// **** abstract class - cannot be instantiated ****
class Shape
{
public:
	// **** getters and setters ****
	virtual void setX(int xCoord) = 0;
	virtual void setY(int yCoord) = 0;

	virtual int getX() = 0;
	virtual int getY() = 0;

	// **** other methods ****
	virtual void draw() = 0;
};

The implementation for the Shape abstract class follows:

#include "pch.h"

// **** abstract class - cannot be instantiated ****

In this case we will only derive the Circle class. The Circle class definition follows:

#pragma once

using namespace std;

#include <iostream>
#include <string>

// **** derived class from Shape ****
class Circle : public Shape
{

private:
	int xCoord;
	int yCoord;
	int radius;

public:

	// **** constructor ****
	Circle(int  xCoord, int yCoord, int radius);

	// **** destructor ****
	~Circle();

	// **** getter and setters ****
	virtual int getX();
	virtual int getY();

	virtual void setX(int xCoord);
	virtual void setY(int yCoord);

	void setRadius(int radius);
	int getRadius();

	// **** other methods ****
	virtual void draw();

	// **** to string ****
	string toString();
};

The Circle class implementation follows:

#include "pch.h"

// **** constructor ****
Circle::Circle(int xCoord, int yCoord, int radius)
{
	this->xCoord = xCoord;
	this->yCoord = yCoord;
	this->radius = radius;
}

// **** destructor ****
Circle::~Circle()
{
}

// **** getter and setters ****
int Circle::getX()
{
	return this->xCoord;
}

int Circle::getY()
{
	return this->yCoord;
}

void Circle::setX(int xCoord)
{
	this->xCoord = xCoord;
}

void Circle::setY(int yCoord)
{
	this->yCoord = yCoord;
}

void Circle::setRadius(int radius)
{
	this->radius = radius;
}

int Circle::getRadius()
{
	return this->radius;
}

// **** to string ****
string Circle::toString()
{
	return "xCoord: " + to_string(this->xCoord) 
		+ " yCoord: " + to_string(this->yCoord) 
		+ " radius: " + to_string(this->radius);
}

// **** other methods ****
void Circle::draw()
{
	cout << "drawing circle at: (" << getX() << "," << getY() << ") with radius: " << getRadius() << endl;
}

The test code for this class follows:

	// **** ****
	Circle circle = Circle(1, 2, 3);
	cout << circle.toString() << endl;
	circle.draw();

The console output for this test follows:

xCoord: 1 yCoord: 2 radius: 3
drawing circle at: (1,2) with radius: 3

We will now revisit and modify the Quad class form the previous post and will make some simplifications and instantiate a couple derived classes. Will start with the Quad class definition:

#pragma once

using namespace std;

#include <iostream>
#include <string>

// **** base class ****
class Quad
{

protected:
	double side1;
	double side2;
	double side3;
	double side4;

public:

	// **** constructor ****
	Quad(double side1, double side2, double side3, double size4);

	// **** destructor ****
	~Quad();

	// **** other methods ****
	virtual void display();
};

Will follow with the base Quad class implementation:

#include "pch.h"

// **** constructor ****
Quad::Quad(double side1, double side2, double side3, double side4)
{
	this->side1 = side1;
	this->side2 = side2;
	this->side3 = side3;
	this->side4 = side4;
}

// **** destructor ****
Quad::~Quad()
{
	// **** to do: free allocated resources **** 
}

// **** other methods ****
void Quad::display()
{
	cout << "Quad with sides: " << this->side1 << " "
		<< this->side2 << " " << this->side3
		<< " " << this->side4 << endl;
}

Let’s define a Trapezoid class which will be derived from the Quad base class:

#pragma once

using namespace std;

#include <iostream>
#include <string>


// **** derived class ****
class Trapezoid : public Quad
{
public:

	// **** constructor ****
	Trapezoid(double side1, double side2, double side3, double side4);

	// **** destructor ****
	~Trapezoid();

	// **** other methods ****
	virtual void display();
};

Will now do the implementation of the Trapezoid class:

#include "pch.h"

// **** constructor ****
Trapezoid::Trapezoid(double side1, double side2, double side3, double side4)
	: Quad(side1, side2, side3, side4)			// call to base classed constructor
{
}

// **** destructor ****
Trapezoid::~Trapezoid()
{
	// **** to do: free allocated resources **** 
}

// **** other methods ****
void Trapezoid::display()
{
	cout << "Trapezoid with sides: " << this->side1 << " "
		<< this->side2 << " " << this->side3
		<< " " << this->side4 << endl;
}

Let’s now derive a Square class from the Quad class:

#pragma once

using namespace std;

#include <iostream>
#include <string>

// **** derived class ****
class Square : public Quad
{
public:

	// **** constructor ****
	Square(double side);

	// **** destructor ****
	~Square();

	// **** other methods ****
	virtual void display();
};

Will follow that with its associated implementation:

#include "pch.h"

// **** constructor ****
Square::Square(double side)
	: Quad(side, side, side, side)
{
}

// **** destructor ****
Square::~Square()
{
	// **** to do: free allocated resources **** 
}

// **** other methods ****
void Square::display()
{
	cout << "Square with sides: " << this->side1 << " "
		<< this->side2 << " " << this->side3
		<< " " << this->side4 << endl;
}

Now we will write some test code:

	// **** experiment with a trapezoid and a square ****
	Quad quad = Quad(1, 2, 3, 4);
	quad.display();
	cout << endl;

	Trapezoid t1(3, 5, 5, 2);
	Square s1(6);
	t1.display();
	s1.display();
	cout << endl;

	// **** let's try with a vector ****
	vector<Quad *> quads;
	quads.push_back(&t1);
	quads.push_back(&s1);

	cout << "quads: " << endl;
	for (int i = 0; i < quads.size(); i++) quads[i]->display();
	cout << endl;

The output for the test code capture in the console of the VS 2017 IDE follows:

Quad with sides: 1 2 3 4

Trapezoid with sides: 3 5 5 2
Square with sides: 6 6 6 6

quads:
Trapezoid with sides: 3 5 5 2
Square with sides: 6 6 6 6

For this last example we will implement an Animal class and derive a Cat and a Dog.

The Animal base class definition follows:

#pragma once

using namespace std;

#include <iostream>
#include <string>

// **** abstract class ****
class Animal
{
public:
	// **** other methdos ****
	virtual void talk() = 0;
};

The Animal implementation of the base class follows:

#include "pch.h"

// **** abstract class - cannot be instantiated ****

The Dog derived class definition follows:

#pragma once

using namespace std;

#include <iostream>
#include <string>

// **** derived class ****
class Dog : public Animal
{
public:
	// **** constructor ****
	Dog();

	// **** destructor ****
	~Dog();

	// **** other methods ****
	virtual void talk();
};

The Dog derived class implementation follows:

#include "pch.h"

// **** constructor ****
Dog::Dog()
{
}

// **** destructor ****
Dog::~Dog()
{
}

// **** other methods ****
void Dog::talk()
{
	cout << "bow wow!!!" << endl;
}

Now let’s define the derived Cat class:

#pragma once

using namespace std;

#include <iostream>
#include <string>

class Cat : public Animal
{
public:
	// **** constructor ****
	Cat();

	// **** destructor ****
	~Cat();

	// **** other methods ****
	virtual void talk();
};

And let’s implement the code for the derived Cat class:

#include "pch.h"

// **** constructor ****
Cat::Cat()
{
}

// **** destructor ****
Cat::~Cat()
{
}

// **** other methods ****
void Cat::talk()
{
	cout << "Meow!!!" << endl;
}

I was going to use the name of my puppy and the name of the neighbor’s cat but decided to just go with generic ones. The test code follows:

	// **** ****
	Dog dog = Dog();
	dog.talk();

	Cat cat = Cat();
	cat.talk();

	cout << endl;

	// **** let's try it with a vector ****
	vector<Animal *> pets;

	pets.push_back(&dog);
	pets.push_back(&cat);

	cout << "pets: " << endl;
	for (int i = 0; i < pets.size(); i++) pets[i]->talk();
	cout << endl;

The console output is:

bow wow!!!
Meow!!!

pets:
bow wow!!!
Meow!!!

I have been thinking that I should post the code on GitHub instead of spending time embedding it in the actual post. I am convinced it will save me several hours per post. Will complete this series embedding the code but on future ones will just post it. In addition I have been learning and experimenting with machine learning (ML) and it seems that including the data is not realistic.

Hope you have enjoyed this post. As usual, if you have comments or questions regarding this or any other post, please leave me a note below.

Keep on learning and experimenting with software;

John

And please follow me on Twitter: @_john_canessa

 

 

 

Leave a Reply

Your email address will not be published.

This site uses Akismet to reduce spam. Learn how your comment data is processed.