- Method Overriding
bool Book::isHeavy() { return numPages > 200; }
bool Text::isHeavy() { return numPages > 300; }
bool Comic::isHeavy() { return numPages > 30; }
Book b = Comic{..., ..., 40, ...};
b.isHeavy(); // calls Book::isHeavy() due to object slicing
Comic c{..., ..., 40, ...};
Comic *cptr{&c};
cptr->isHeavy(); // calls Comic::isHeavy()
Book *bptr{&c};
bptr->isHeavy(); // calls Book::isHeavy(), no slicing has occured
// Bool:isHeavy() is called since the compiler looks at the declared type of the ptr
- To change this behaviour, we want to use Dynamic Dispatch using
virtual
// Book class
class Book {
std::string title, author;
int length;
protected:
int getLength() const;
public:
Book(const std::string &title, const std::string &author, int length);
std::string getTitle() const;
std::string getAuthor() const;
virtual bool isHeavy() const;
};
// Comic class
class Comic: public Book {
std::string hero;
public:
Comic(const std::string &title, const std::string &author, int length, const std::string &hero);
bool isHeavy() const override;
std::string getHero() const;
};
Now the following code has different behaviour due to
virtual
andoverride
in the above classes
Comic c{..., ..., 40, ...};
Comic *cp{&c};
Book *bp{&c};
Book &br{c};
cp->isHeavy(); // calls Comic::isHeavy()
bp->isHeavy(); // calls Comic::isHeavy()
br.isHeavy(); // calls Comic::isHeavy()
- A virtual method is dispatched dynamically
- The decision on which method to call is based on the runtime type of the object
- dynamic dispatch has a cost since it is determined at runtime
- CS744
// The objects will be sliced, but the correct isHeavy will be called
Book *collection[20];
for (int i = 0; i < 20; ++i) cout << collection[i]->isHeavy();
The ability to accommodate multiple types within the same abstraction
- collection is a polymorphic array
// Must use references to avoid slicing to occur on the params
// ostream is polymorphic
ostream &operator<<(ostream &, Student &);
- When an object of a derived class is destroyed:
- subclass destructor body runs
- subclass fields are destroyed(reverse declaration order, opposite to construction)
- superclass destructor is called
- memory is deallocated
// Run with valgrind
// leaks memory
class X {
int *x;
public:
X(int n): x{new int [n]} {}
~X() { delete [] x; }
};
class Y: public X {
int *y;
public:
Y(int n, int m): X{n}, y{new int [m]} {}
~Y() { delete [] y; }
};
int main () {
X x{5};
Y y{5, 10};
X *xp = new Y{5, 10};
// calls class X destructor but not class Y destructor
// destructor is statically dispatched
delete xp;
}
// Run with valgrind
// no memory leak
class X {
int *x;
public:
X(int n): x{new int [n]} {}
virtual ~X() { delete [] x; }
};
class Y: public X {
int *y;
public:
Y(int n, int m): X{n}, y{new int [m]} {}
~Y() { delete [] y; } // doens't need virtual keyword since it was declared in class X
};
int main () {
X x{5};
Y y{5, 10};
X *xp = new Y{5, 10};
// calls class Y destructor since X destructor is virtal
// destructor is dynamically dispatched
delete xp;
}
- If you don't want your class to be subclassed, declare the class
final
- This is default in
kotlin
but notjava
- This is default in
class Y final : public X {
};
- Note that final can be a field name, it is one of the keywords that only counts as a keyword if it is in a specific location, this is to maintain backwards compatibility
class Student {
public:
// fees has no default impl
virtual int fees();
};
class Coop : public Student {
public:
int fees() override {
}
};
class Regular : public Student {
public:
int fees() override {
}
};
- We want Student::fees to not have an implementation
- Make it Pure Virtual
class Student {
public:
// Pure Virtual method
virtual int fees() = 0;
};
virtual
methods MAY be overridden by subclassesPure Virtual
methods MUST be overridden by subclasses to be considered concrete- Student is not a concrete class since it has a Pure Virtual method
abstract
class
- A class with even a single Pure Virtual method is abstract and cannot be instantiated
Student s: // will not compile
-
Abstract classes are used to organize types
- shared fields/methods
- polymorphism
-
A concrete class declares no new Pure Virtual methods and overrides all inherited Pure Virtual methods
- Both
virtual
andPure Virtual
use italics Abstract
class name use italicsstatic
uses bold
// A non generic Stack class
class Stack {
int count;
int capacity;
int *contents;
public:
int pop();
void push(int);
int top();
~Stack();
};
- To make this Stack class generic, we can use C++ templates
- Template class is parameterized on a type
- The type would be
int
in the above class
- The type would be
- Template class is parameterized on a type
// A generic Stack class
template <typename T>
class Stack {
int count;
int capacity;
T *contents;
public:
T pop();
void push(T);
T top();
~Stack();
};
Stack<int> sInts;
Stack<string> sStrings;