Introduction to C++: Part 3
Tutorial Outline: Part 3
Inheritance and overrides
Virtual functions and interfaces
The formal concepts in OOP
Polymorphism
Next up: Polymorphism
Encapsulation
OOP
Inheritance
Abstraction
Using subclasses
A function that takes a superclass
argument can also be called with void PrintArea(Rectangle &rT) {
a subclass as the argument. cout << rT.Area() << endl ;
}
The reverse is not true – a int main() {
function expecting a subclass Rectangle rT(1.0,2.0) ;
Square sQ(3.0) ;
argument cannot accept its
PrintArea(rT) ;
superclass. PrintArea(sQ) ;
}
Copy the code to the right and
add it to your main.cpp file. The PrintArea function
can accept the Square
object sQ because
Square is a subclass of
Rectangle.
Overriding Methods class Super {
public:
Sometimes a subclass needs to have the void PrintNum() {
same interface to a method as a cout << 1 << endl ;
superclass with different functionality. }
} ;
This is achieved by overriding a method. class Sub : public Super {
public:
// Override
Overriding a method is simple: just re- void PrintNum() {
cout << 2 << endl ;
implement the method with the same }
name and arguments in the subclass. } ;
Super sP ;
sP.PrintNum() ; // Prints 1
Sub sB ;
In C::B open project: sB.PrintNum() ; // Prints 2
CodeBlocks Projects Part 2 Virtual Method Calls
Overriding Methods
class Super {
public:
Seems simple, right? void PrintNum() {
cout << 1 << endl ;
}
} ;
class Sub : public Super {
public:
// Override
void PrintNum() {
cout << 2 << endl ;
}
} ;
Super sP ;
sP.PrintNum() ; // Prints 1
Sub sB ;
sB.PrintNum() ; // Prints 2
class Super {
How about in a function call… public:
void PrintNum() {
cout << 1 << endl ;
}
} ;
Using a single function to operate
on different types is class Sub : public Super {
public:
polymorphism. // Override
void PrintNum() {
cout << 2 << endl ;
}
Given the class definitions, what } ;
is happening in this function call? void FuncRef(Super &sP) {
sP.PrintNum() ;
}
Super sP ;
“C++ is an insult to the human brain” Func(sP) ; // Prints 1
– Niklaus Wirth (designer of Pascal) Sub sB ;
Func(sB) ; // Hey!! Prints 1!!
void FuncRef(Super &sP) {
sP.PrintNum() ;
Type casting }
The Func function passes the argument as a reference (Super &sP).
What’s happening here is dynamic type casting, the process of converting from
one type to another at runtime.
Same mechanism as the dynamic_cast<type>() function
The incoming object is treated as though it were a superclass object in
the function.
When methods are overridden and called there are two points where
the proper version of the method can be identified: either at compile
time or at runtime.
class SuperVirtual
{
public:
Virtual methods virtual void PrintNum()
{
When a method is labeled as virtual and cout << 1 << endl ;
}
overridden the compiler will generate } ;
code that will check the type of an object
class SubVirtual : public SuperVirtual
at runtime when the method is called. {
public:
// Override
The type check will then result in the virtual void PrintNum()
expected version of the method being {
cout << 2 << endl ;
called. }
} ;
When overriding a virtual method in a void Func(SuperVirtual &sP)
{
subclass, it’s a good idea to label the sP.PrintNum() ;
method as virtual in the subclass as well. }
…just in case this gets subclassed again! SuperVirtual sP ;
Func(sP) ; // Prints 1
SubVirtual sB ;
Func(sB) ; // Prints 2!!
Early (static) vs. Late (dynamic) binding
Leaving out the virtual keyword on a Making a method virtual adds code
method that is overridden results in the behind the scenes (that you, the
compiler deciding at compile time which programmer, never interact with directly)
version (subclass or superclass) of the Lookups in a hidden table, called the
method to call. vtable, are done to figure out what version
This is called early or static binding. of the virtual method should be run.
At compile time, a function that takes a
superclass argument will only call the This is called late or dynamic binding.
non-virtual superclass method under
early binding. There is a small performance penalty for
late binding due to the vtable lookup.
This only applies when an object is
referred to by a reference or pointer.
Behind the scenes – vptr and vtable
Func(SuperVirtual &sP)
C++ classes have a hidden pointer (vptr) sP is a reference to a…
generated that points to a table of virtual
methods associated with a class (vtable).
SuperVirtual SubVirtual
When a virtual class method (base class
or its subclasses) is called by reference ( SuperVirtual’s SubVirtual’s
vptr vptr
or pointer) when the program is running
the following happens:
The object’s class vptr is followed to its class
vtable
Vtable Vtable
The virtual method is looked up in the vtable
and is then called.
One vptr and one vtable per class so minimal & SuperVirtual::PrintNum() & SubVirtual::PrintNum()
memory overhead
If a method override is non-virtual it won’t be in
the vtable and it is selected at compile time.
Let’s run this through the debugger
Open the project: Parts 2- Make sure the “Watches”
3/Virtual Method Calls. debugging window is open.
Everything here is
implemented in one big
main.cpp
Place a breakpoint at the first
line in main() and in the two
implementations of Func()
When to make methods virtual
If a method will be (or might be) Constructors are never virtual in C++.
overridden in a subclass, make it virtual Destructors in a base class should
There is a minor performance penalty. always be virtual.
Will that even matter to you? Also – if any method in a class is virtual,
i.e. Have you profiled and tested your code to
show that virtual method calls are a performance
make the destructor virtual
issue? These are important when dealing with
When is this true? objects via reference and it avoids some
Almost always! Who knows how your code will subtleties when manually allocating
be used in the future? memory.
Why all this complexity?
void FuncEarly(SuperVirtual &sP) void FuncLate(SuperVirtual sP)
{ {
sP.PrintNum() ; sP.PrintNum() ;
} }
Called by reference – late binding Called by value – early binding to
to PrintNum() PrintNum even though it’s virtual!
Late binding allows for code libraries to be updated for new functionality. As methods are identified
at runtime the executable does not need to be updated.
This is done all the time! Your C++ code may be, for example, a plugin to an existing simulation
code.
Greater flexibility when dealing with multiple subclasses of a superclass.
Most of the time this is the behavior you are looking for when building class hierarchies.
Shape
virtual float Area() {}
Remember the Deadly Diamond of
Death? Let’s explain.
Look at the class hierarchy on the right.
Square and Circle inherit from Shape
Squircle inherits from both Square and Circle Square
Circle
Syntax:
virtual float
class Squircle : public Square, public Circle Area() {…} virtual float
Area() {…}
The Shape class implements an empty
Area() method. The Square and Circle
classes override it. Squircle does not.
Under late binding, which version of Area
is accessed from Squircle?
Square.Area() or Circle.Area()?
Squircle
Interfaces
Shape Log
Another pitfall of multiple inheritance: the
fragile base class problem.
If many classes inherit from a single base
(super) class then changes to methods in the
base class can have unexpected
consequences in the program. Square Circle
This can happen with single inheritance but it’s
much easier to run into with multiple
inheritance.
Interfaces are a way to have your Example: for debugging you’d like each class
classes share behavior without them to have a Log() method that would write some
sharing actual code. info to a file.
Implement with an interface.
Gives much of the benefit of multiple
inheritance without the complexity and
pitfalls
#ifndef SQUARE_H
Interfaces #define SQUARE_H
#include "rectangle.h"
An interface class in C++ is called a pure virtual class.
class Log {
It contains virtual methods only with a special syntax. virtual void LogInfo()=0 ;
Instead of {} the function is set to 0. };
Any subclass needs to implement the methods!
Modified square.h shown. class Square : public Rectangle, Log
{
What happens when this is compiled? public:
(…error…) Square(float length);
include/square.h:10:7: note: because the following virtual virtual ~Square();
functions are pure within 'Square': // virtual void LogInfo() {}
class Square : public Rectangle, Log
^ protected:
include/square.h:7:18: note: virtual void Log::LogInfo()
virtual void LogInfo()=0 ; private:
};
Once the LogInfo() is uncommented it will compile.
#endif // SQUARE_H
C++ offers another fix for the diamond problem, Virtual inheritance. See: https://en.wikipedia.org/wiki/Virtual_inheritance
Putting it all together
Shape
Now let’s revisit our Shapes
project. ???
In the directory of C::B Part 2-3
projects, open the “Shapes with Rectangle Circle
Circle” project.
This has a Shape base class with a
Rectangle and a Square
Add a Circle class to the class
Square
hierarchy in a sensible fashion.
Hint: Think first, code second.
New pure virtual Shape class
#ifndef SHAPE_H
Slight bit of trickery: #define SHAPE_H
An empty constructor is defined in shape.h
No need to have an extra shape.cpp file if these
functions do nothing! class Shape
{
public:
Q: How much code can be in the header file? Shape() {}
virtual ~Shape() {}
A: Most of it with some exceptions.
.h files are not compiled into .o files so a virtual float Area()=0 ;
header with a lot of code gets re-compiled protected:
every time it’s referenced in a source file.
private:
};
#endif // SHAPE_H
Give it a try
Add inheritance from Shape If you just want to see a
to the Rectangle class solution, open the project
Add a Circle class, inheriting “Shapes with Circle solved”
from wherever you like.
Implement Area() for the
Circle
A Potential Solution
Shape
A Circle has one dimension
(radius), like a Square.
Would only need to override the
Area() method Rectangle
But…
Would be storing the radius in the
members m_width and m_length.
This is not a very obvious to
someone else who reads your code.
Square
Maybe:
Change m_width and m_length
names to m_dim_1 and m_dim_2? Circle
Just makes everything more muddled!
A Better Solution
Shape
Inherit separately from the Shape
base class
Seems logical, to most people a
circle is not a specialized form of Rectangle Circle
rectangle…
Add a member m_radius to store
the radius.
Implement the Area() method
Square
Makes more sense!
Easy to extend to add an Oval
class, etc.
#ifndef CIRCLE_H
#define CIRCLE_H
New Circle class
#include "shape.h"
Also inherits from Shape class Circle : public Shape
Adds a constant value for p {
Constant values can be defined right in the public:
header file. Circle();
Circle(float radius) ;
If you accidentally try to change the value of PI
virtual ~Circle();
the compiler will throw an error.
virtual float Area() ;
const float PI = 3.14;
float m_radius ;
protected:
private:
};
#endif // CIRCLE_H
#include "circle.h"
Circle::Circle()
{
//ctor
}
circle.cpp
Circle::~Circle()
Questions? {
//dtor
}
// Use a member initialization list.
Circle::Circle(float radius) : m_radius{radius}
{}
float Circle::Area()
{
// Quiz: what happens if this line is
// uncommented and then compiled:
//PI=3.14159 ;
return m_radius * m_radius * PI ;
}
Quiz time!
void PrintArea(Shape &shape) {
cout << "Area: " << shape.Area() << endl ;
}
int main()
What happens behind {
the scenes when the Square sQ(4) ;
Circle circ(3.5) ;
function PrintArea is Rectangle rT(21,2) ;
called?
// Print everything
How about if PrintArea’s PrintArea(sQ) ;
argument was instead: PrintArea(rT) ;
PrintArea(circ) ;
return 0;
void PrintArea(Shape shape) }
Quick mention…
Aside from overriding functions it Syntax:
is also possible to override MyClass operator*(const MyClass& mC) {...}
operators in C++. Recommendation:
As seen in the C++ string. The +
Generally speaking, avoid this. This
operator concatenates strings:
is an easy way to generate very
string str = "ABC" ; confusing code.
str = str + "DEF" ;
// str is now "ABCDEF" A well-named function will almost
always be easier to understand than
It’s possible to override +,-,=,<,>, an operator.
brackets, parentheses, etc. An exceptions is the assignment
operator: operator=
Summary
C++ classes can be created in hierarchies via Subclasses can override a superclass
inheritance, a core concept in OOP. method for their own purposes and can still
Classes that inherit from others can make use explicitly call the superclass method.
of the superclass’ public and protected Abstraction means hiding details when they
members and methods don’t need to be accessed by external code.
You write less code! Reduces the chances for bugs.
Virtual methods should be used While there is a lot of complexity here – in
whenever methods will be overridden in terms of concepts, syntax, and application –
keep in mind that OOP is a highly successful
subclasses.
way of building programs!
Avoid multiple inheritance, use interfaces
instead.