0% found this document useful (0 votes)
14 views

Introduction To C++ Part 3

The document discusses object-oriented programming concepts in C++, including polymorphism, inheritance, abstraction, and encapsulation. It covers using subclasses and overriding methods, and explains the difference between early and late binding. Virtual functions are described as functions that are looked up dynamically at runtime based on the actual object type. This allows a function taking a base class argument to call the subclass version of a virtual method. The document also provides an example of how virtual tables and virtual pointers work behind the scenes to implement dynamic binding in C++.

Uploaded by

Cosmos 369
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
14 views

Introduction To C++ Part 3

The document discusses object-oriented programming concepts in C++, including polymorphism, inheritance, abstraction, and encapsulation. It covers using subclasses and overriding methods, and explains the difference between early and late binding. Virtual functions are described as functions that are looked up dynamically at runtime based on the actual object type. This allows a function taking a base class argument to call the subclass version of a virtual method. The document also provides an example of how virtual tables and virtual pointers work behind the scenes to implement dynamic binding in C++.

Uploaded by

Cosmos 369
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 27

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.

You might also like