Previous Lecture Lecture 10 Next Lecture

Lecture 10, Thu 10/31

Inheritance and Polymorphism

Inheritance

Example (Person / Student Inheritance)

// Person.h
#ifndef PERSON_H
#define PERSON_H
class Person {
public:
	Person(std::string name, int age);
	std::string getName();
	int getAge();
	void setName(std::string name);
	void setAge(int age);
private:
	std::string name;
	int age;
};
#endif
// Student.h
#ifndef STUDENT_H
#define STUDENT_H
#include "Person.h"

class Student : public Person {
public:
	Student(std::string name, int age, int studentId);
	int getStudentId();
	void setStudentId(int id);
private:
	int studentId;
};
#endif
// Person.cpp
#include <string>
#include "Person.h"

using namespace std;

Person::Person(string name, int age) {
	this->name = name;
	this->age = age;
}

string Person::getName() { return name; }

int Person::getAge() { return age; }

void Person::setName(string name) { this->name = name; }

void Person::setAge(int age) { this->age = age; }
// Student.cpp
#include <string>
#include "Student.h"

using namespace std;

Student::Student(string name, int age, int id) : Person(name, age){
	studentId = id;
}

int Student::getStudentId() { return studentId; }

void Student::setStudentId(int id) { studentId = id; }

Note:

Note on constructors and inheritance

Redefining inherited functions

// in Student.h
std::string getName();

// in Student.cpp
string Student::getName() {
    //return "STUDENT: " + name; //ERROR, name isn’t inherited
    //return "STUDENT: " + getName(); // ERROR Student::getName() is called.
    return "STUDENT: " + Person::getName(); // OK!
} 

Inheritance Types

Person p1("Chris Gaucho", 21);
Student s1("John Doe", 22, 12345678);

Person p2  = s1; // Legal, a student is a person
Student s2 = p1; // illegal, a person may not be a student

Memory Slicing

cout << p2.getName() << endl;
cout << p2.getAge() << endl;
cout << p2.getStudentId() << endl; // ERROR! p2 doesn’t have studentID

Pointers of base types

Person* p1 = new Person("R1", 10);
Student* s1 = new Student("JD", 21, 1234567);

// Student* s2 = p1;    // Illegal!
Person* p2 = s1;        // OK.
cout << p2->getName() << endl;
cout << p2->getAge() << endl;
//cout << p2->getStudentId() << endl; // illegal! getStudentId() is not known in Person
cout << s1->getStudentId() << endl; // OK.

Destructors and Inheritance

Example

// in Student.h
~Student();
// in Student.cpp
Student::~Student() {
	cout << "in Student Destructor" << endl;
}
// in Person.h
~Person();
// in Person.cpp
Person::~Person() {
	cout << "in Person destructor" << endl;
}
// in main.cpp
Person* p1 = new Person("R1", 10);
Student* s1 = new Student("JD", 21, 1234567);
delete p1;
cout << "---" << endl;
delete s1;

Person* p2 = new Student("Student", 25,7654321);

// calls Person's destructor only
delete p2;

// But student object memory has not been destructed properly in this case.
// Polymorphism and virtual destructors will allows us to call
// the appropriate destructor in this scenario...

Polymorphism (Dynamic Binding) - Read PS 15.3

Example: Adding a toString function to both Person and Student

class Person {
public:
...
	std::string toString();
...
};

string Person::toString() {
	return "Person - Name: " + name + ", age: " + to_string(age);
}

class Student {
public:
...
	std::string toString();
...
};

string Student::toString() {
    return "Student - name: " + getName() + ", age: " +
      to_string(getAge()) + ", " + to_string(studentId);
}

// main.cpp
Person p1("Richert", 32);
cout << p.toString() << endl;

Student s1("John Doe", 21, 12345678);
cout << s.toString() << endl;

Person* p2 = new Person("Someone", 18);
cout << p2->toString() << endl;

Student* s2 = new Student("Jane Doe", 19, 23456789);
cout << s2->toString() << endl;

Person* p3 = new Student("Person Student", 21, 11111111);
cout << p3->toString() << endl;//uses Person::toString(). Not polymorphic

The above example uses Person’s toString function instead of Student’s even though p3 points to a Student object.

// add the keyword "virtual" to the toString functions
class Person {
public:
	virtual std::string toString();
...
};

class Student {
public:
	virtual std::string toString();
...
};

Notes:

Example of Object slicing

Person p4 = Student("Person Student 2", 21, 1000000);
cout << p4.toString() << endl; // which toString is called?

Example of Polymorphism with Objects on the Stack

void f1(Person& p) {
    cout << p.toString() << endl;
}
int main () {
    Student r = Student("Richert", 21, 1234567);
    cout << r.toString() << endl; // Calls Student::toString

    Person s = Student("T", 12, 1111112);
    cout << s.toString() << endl; // calls Person::toString() (object sliced)

    Person* t = new Student("R", 10, 654321);
    cout << t->toString() << endl; // calls Student::toString (polymorphic)

    f1(r); // calls Student::toString()

    f1(s); // calls Person::toString() since a person is constructed in memory

    f1(*t); // calls Student::toString() since a student is constructed on heap
}

Inheritance and Memory Layout Example

class X {
    public:
    X() { a = 10; b = 20; }
    short a;    // change this to int
    int b;      // change this to short
                // observe how class Y is memory padded
};

class Y : public X {
    public:
    Y() : X() { c = 30; d = 40; }
    short c;
    short d;
};

int main() {
    X x;
    Y y;
    X z = y;

    cout << "&x = " << &x << endl;
    cout << "&x.a = " << &x.a << endl;
    cout << "&x.b = " << &x.b << endl;
    cout << "---" << endl;
    cout << "&y = " << &y << endl;
    cout << "&y.a = " << &y.a << endl;
    cout << "&y.b = " << &y.b << endl;
    cout << "&y.c = " << &y.c << endl;
    cout << "&y.d = " << &y.d << endl;
    cout << "---" << endl;
    cout << "&z = " << &z << endl;
    cout << "&z.a = " << &z.a << endl;
    cout << "&z.b = " << &z.b << endl;

    return 0;
}

Pure Virtual Functions and Abstract Classes

There are times where it doesn’t make sense for a base class to have a virtual function.

Example

class Shape {
public:
	virtual double area();
};

Example (defining a pure virtual function)

class Shape {
public:
	virtual double area() = 0; // pure virtual function
};

You can have references or pointers to abstract classes (i.e. Shape* s is legal and can point to objects of any child class).

class Rectangle : public Shape {
public:
	Rectangle() {}
	Rectangle(double length, double width);
	virtual double area();
private:
	double length;
	double width;
};

Rectangle::Rectangle(double length, double width) {
	this->length = length;
	this->width = width;
}

double Rectangle::area() {
	cout << "In Rectangle::area()" << endl;
	return length * width;
}

class Square : public Shape {
public:
	Square(double side);
	virtual double area();
private:
	double side;
};

Square::Square(double side) {
	this->side = side;
}

double Square::area() {
	cout << "In Square::area()" << endl;
	return side * side;
}

Array of Pointers

Shape shapes[2]; // ERROR, tries to construct a shape object.
Shape* shapes[2]; // OK

shapes[0] = new Square(10);
shapes[1] = new Rectangle(3, 4);

cout << shapes[0]->area() << endl;
cout << shapes[1]->area() << endl;

The correct area() function will be called on a sub class object if that object is pointed to by Shape* even if Shape does not have an area() definition.

Example with various scenarios

	Rectangle* r = new Rectangle[100]; // Rectangle array on the heap
	//r[0] = new Rectangle(2,2); // ERROR, expects objects, not pointers
	r[0] = Rectangle(2,2); // OK, r[0] is an object

	//Rectangle* r1 = new Rectangle*[100]; // ERROR
	Rectangle** r1 = new Rectangle*[100]; // OK, r1 is a pointer to an array of pointers
	r1[0] = new Rectangle(2,3); // r1[0] is a pointer
	cout << r1[0]->area() << endl;

	Shape* s = new Rectangle(5,5);
	cout << s->area() << endl; // Works as expected.

	Shape* s1[100]; // Array declared on the stack
	s1[0] = new Rectangle(6,6);
	cout << s1[0]->area() << endl; // Works as expected.

	delete [] r;	// OK, deletes the array of Rectangles on heap
	delete [] r1;
	// OK, deletes array of Rectangle pointers on heap
	// Be careful, Rectangle objects that elements in r1
	// point to may still be on the heap.
	// You may need to manually delete these as well.

	delete s1[0];
	// OK, deleting a Rectangle object on the heap
	// may receive a warning if Shape does not have a virtual destructor

	//delete [] s1;	// ERROR. Trying to delete something on the stack.