Lecture4.pdf

C++ Programming Languages
Lecturer:
Omid Jafarinezhad
Fall 2013
Lecture 4: Object Oriented Programming
Department of Computer Engineering
1
Outline
• object
object--oriented programming
– Classes and class members
– Access functions and encapsulation
– Constructors and Destructors
– Class code and header files
– Operator overloading
– Composition
– Inheritance
– Virtual Functions
– Template classes
– Exceptions
2
Classes and class members
struct DateStruct
{
int nMonth;
int nDay;
int nYear;
};
// Here is a function to initialize a date
void SetDate(DateStruct &sDate, int nMonth, int nDay, int nYear)
{
sDate.nMonth = nMonth;
sDate.nDay = nDay;
sDate.nYear = nYear;
}
// … In main …
DateStruct sToday;
// Initialize it manually
sToday.nMonth = 10;
sToday.nDay = 14;
sToday.nYear = 2040;
SetDate((sToday
SetDate
sToday,, 10
10,, 14
14,, 2020
2020);
);
3
Classes and class members
struct DateStruct
{
int nMonth;
int nDay;
int nYear;
};
class Date
{
public::
public
int m_nMonth
m_nMonth;;
int m_nDay
m_nDay;;
int m_nYear
m_nYear;;
};
Date cToday; // declare a Object of class Date
// Assign values to our members using the member selector operator (.)
cToday.m_nMonth = 10;
cToday.m_nDay = 14;
cToday.m_nYear = 2020;
4
Classes and class members
struct DateStruct
{
int nMonth;
int nDay;
int nYear;
};
// Here is a function to initialize a date
void SetDate(DateStruct &sDate,
int nMonth,
int nDay,
int nYear)
{
sDate.nMonth = nMonth;
sDate.nDay = nDay;
sDate.nYear = nYear;
}
DateStruct sToday;
SetDate((sToday
SetDate
sToday,, 10
10,, 14
14,, 2020
2020);
);
class Date
{
public:
int m_nMonth;
int m_nDay;
int m_nYear;
// Member function
void SetDate
SetDate((int nMonth
nMonth,,
int nDay
nDay,, int nYear
nYear))
{
m_nMonth = nMonth
nMonth;;
m_nDay = nDay
nDay;;
m_nYear = nYear
nYear;;
}
};
Date cToday;
// call SetDate
SetDate()
() on cToday
cToday.SetDate(10, 14, 2020);
5
Classes and class members
#include <iostream>
class Employee
{
public:
public
char m_strName[25];
int m_nID;
double m_dWage;
Employee cAlex;
cAlex.SetInfo("Alex",
cAlex.SetInfo
("Alex", 1, 25
25..00
00);
);
Employee cJoe;
cJoe.SetInfo("Joe", 2, 22.25);
cAlex.Print();
cAlex.Print();
cJoe.Print();
// Set the employee information
void SetInfo(char *strName, int nID, double dWage) {
strncpy(m_strName, strName, 25);
m_nID = nID;
m_dWage = dWage;
}
// Print employee information to the screen
void Print() {
using namespace std;
cout << "Name: " << m_strName << " Id: " <<
m_nID << " Wage: $" << m_dWage << endl;
}
};
6
Public vs private access specifiers
class Access
{
int m_nA; // private by default
int GetA() { return m_nA; } // private by default
private:
int m_nB; // private
int GetB() { return m_nB; } // private
protected:
int m_nC; // protected
int GetC() { return m_nC; } // protected
public:
int m_nD; // public
int GetD() { return m_nD; } // public
};
Access cAccess;
// ok because m_nD is public
cAccess.m_nD = 5;
// ok because GetD
GetD()
() is public
std::cout << cAccess.GetD();
// WRONG because m_nA is private
cAccess.m_nA = 2;
// WRONG because GetB
GetB()
() is private
std::cout << cAccess.GetB();
7
Access functions and encapsulation
class Date
{
private:
int m_nMonth;
int m_nDay;
int m_nYear;
public:
// Getters
int Get
GetMonth() { return m_nMonth; }
int GetDay() { return m_nDay; }
int GetYear() { return m_nYear; }
// Setters
void Set
SetMonth(int
int nMonth) { m_nMonth = nMonth; }
void SetDay(int nDay) { m_nDay = nDay; }
void SetYear(int nYear) { m_nYear = nYear; }
};
8
Public vs private access specifiers
class Change
{
private:
int m_nValue
m_nValue;;
class Change
{
public:
int m_nValue
m_nValue;;
};

int main()
{
Change cChange;
cChange.m_nValue = 5;
std::cout << cChange.m_nValue
<< std::endl;
};
public:
void SetValue
SetValue((int nValue
nValue)) { m_nValue = nValue
nValue;; }
int GetValue
GetValue()
() { return m_nValue
m_nValue;; }
};
int main()
{
Change cChange;
cChange.SetValue((5);
cChange.SetValue
std::cout << cChange.GetValue
cChange.GetValue()
()
<< std::endl;
}
9
Constructors
• A constructor is a special kind of class member function that is
executed when an object of that class is instantiated
• Constructors are typically used to initialize member variables of
the class to appropriate default values, or to allow the user to
easily initialize those member variables to whatever values are
desired
• Constructors have specific rules for how they must be named:
– Constructors should always have the same name as the class
– Constructors have no return type (not even void)
• A constructor that takes no parameters (or has all optional
parameters) is called a default constructor
10
Constructors
class Fraction
{
private:
int m_nNumerator;
int m_nDenominator;
Fraction cDefault
cDefault;; // calls Fraction() constructor
public:
Fraction() // default constructor
{
m_nNumerator = 0;
m_nDenominator = 1;
}
int GetNumerator() { return m_nNumerator; }
int GetDenominator() { return m_nDenominator; }
// …
};
11
Constructors with parameters
class Fraction
Fraction cDefault
cDefault;; // calls Fraction() constructor
{
Fraction cFiveThirds
cFiveThirds((5, 3); // calls Fraction(int
Fraction(int,, int
int)) constructor
private:
Fraction Six(6
Six(6); // calls Fraction(int
Fraction(int,, int
int)) constructor
int m_nNumerator;
int m_nDenominator;
public:
Fraction() { // default constructor
m_nNumerator = 0;
m_nDenominator = 1;
}
// Constructor with parameters
Fraction(int nNumerator
nNumerator,, int nDenominator = 1)
{
assert(nDenominator != 0);
m_nNumerator = nNumerator;
m_nDenominator = nDenominator;
}
int GetNumerator() { return m_nNumerator; }
int GetDenominator() { return m_nDenominator; }
// …
12
};
Private constructors
class Book
{
private:
int m_nPages;
// This constructor can only be used by Book's members
Book() // private default constructor
{
m_nPages = 0;
}
public:
// This constructor can be used by anybody
Book(int
Book(
int nPages
nPages)) // public nonnon-default constructor
{
m_nPages = nPages;
}
};
Book cMyBook
cMyBook;; // fails because default constructor Book() is private
Book cMyOtherBook
cMyOtherBook((242
242);
); // ok because Book(int
Book(int)) is public
13
Constructor chaining issues
class Foo
{
public:
Foo()
Foo
() {
// code to do A
}
Foo((int nValue
Foo
nValue))
{
// code to do A
// code to do B
}
};
class Foo
{
public:
Foo()
Foo
() {
Init();
}
Foo((int nValue
Foo
nValue)) {
Init();
// code to do B
}
void Init()
{
// code to do A
}
};
// C++11
C++11 :
// delegating constructors
class Foo
{
public:
Foo()
Foo
() {
// code to do A
}
Foo((int nValue
Foo
nValue)) : Foo
Foo()
()
{
// code to do B
}
};
14
Destructors
• destructor is called when an object is destroyed
• Like constructors, destructors have specific
naming rules:
– The destructor must have the same name as the
class, preceded by a tilde (~)
– The destructor can not take arguments
• implies that only one destructor may exist per class,
class as
there is no way to overload destructors since they can not
be differentiated from each other based on arguments
– The destructor has no return type
15
Destructors
int main() {
MyString cMyName("Alex"); // call constructor
std::cout << "My name is: " << cMyName.GetString();
return 0;
} // cMyName destructor called here!
class MyString
{
private:
private
char *m_pchString;
int m_nLength;
public:
MyString(const
MyString
(const char *pchString
*pchString="")
="") {
m_nLength = strlen(pchString) + 1; //Plus one character for a terminator
m_pchString = new char[m_nLength];
strncpy(m_pchString, pchString, m_nLength);
m_pchString[m_nLength-1] = '\0'; // Make sure the string is terminated
}
~MyString
MyString()
() { // destructor
delete[] m_pchString
m_pchString;;
m_pchString = 0;
// We need to deallocate our buffer
// Set m_pchString to null just in case
}
char* GetString() { return m_pchString; }
int GetLength() { return m_nLength; }
};
16
The hidden “this” pointer
class Simple
{
private:
int m_nID
m_nID;;
public:
Simple(int nID)
{
SetID(nID);
}
void SetID(int nID) {this
this--> m_nID = nID; }
int GetID() { return this
this-->m_nID; }
};
void SetID(int nID) { m_nID = nID; }
// becomes (by compiler):
void SetID(Simple*
Simple* const this,
this int nID) { this
this-->m_nID = nID; }
17
The hidden “this” pointer
class Calc
{
private:
int m_nValue;
public:
Calc() { m_nValue = 0; }
void Add(int nValue) { m_nValue += nValue; }
void Sub(int nValue) { m_nValue -= nValue; }
void Mult(int nValue) { m_nValue *= nValue; }
int GetValue() { return m_nValue; }
};
Calc cCalc;
cCalc.Add(5);
cCalc.Sub(3);
cCalc.Mult(4);
18
The hidden “this” pointer
class Calc
{
private:
int m_nValue;
public:
Calc() { m_nValue = 0; }
Calc& Add(int nValue) { m_nValue += nValue; return *this; }
Calc& Sub(int nValue) { m_nValue -= nValue; return *this; }
Calc& Mult(int nValue) { m_nValue *= nValue; return *this; }
int GetValue() { return m_nValue; }
};
Calc cCalc;
cCalc.Add((5).Sub(
cCalc.Add
).Sub(3
3).
).Mult
Mult((4);
19
Class code and header files
class Date
{
private:
int m_nMonth;
int m_nDay;
int m_nYear;
Date() { } // private default constructor
public:
Date(int nMonth, int nDay, int nYear) {
Date
SetDate(nMonth, nDay, nYear);
}
void SetDate(int nMonth, int nDay, int nYear) {
m_nMonth = nMonth;
m_nDay = nDay;
m_nYear = nYear;
}
int GetMonth() { return m_nMonth; }
int GetDay() { return m_nDay; }
int GetYear() { return m_nYear; }
};
// Date.h
#ifndef DATE_H
#define DATE_H
// class interface
// class declaration
#endif
// Date.cpp
// class implementation
20
Class code and header files
//Date.h
//Date.h
#ifndef DATE_H
#define DATE_H
class Date {
private:
private
int m_nMonth;
int m_nDay;
int m_nYear;
Date() ;
public:
public
Date(int
Date(
int nMonth
nMonth,, int nDay
nDay,, int nYear
nYear);
);
void SetDate
SetDate((int nMonth
nMonth,, int nDay
nDay,, int nYear
nYear);
);
int GetMonth
GetMonth()
() ;
int GetDay
GetDay()
() ;
int GetYear
GetYear()
() ;
};
#endif
21
Class code and header files
//Date.cpp
#include "Date.h
"Date.h""
// Date constructor
Date::Date(){}
Date::Date(int nMonth, int nDay, int nYear)
Date::
{
SetDate(nMonth, nDay, nYear);
}
// Date member function
void Date::
Date::SetDate(int nMonth, int nDay, int nYear)
{
m_nMonth = nMonth;
m_nDay = nDay;
m_nYear = nYear;
}
int Date:: GetMonth() { return m_nMonth; }
int Date:: GetDay() { return m_nDay; }
int Date:: GetYear() { return m_nYear; }
};
22
Const class objects and member functions
• If a class is not initialized using a parameterized constructor, a
public default constructor must be provided — if no public
default constructor is provided in this case, a compiler error
will occur
• Once a const class object has been initialized via constructor
constructor,
any attempt to modify the member variables of the object is
disallowed
const int nValue = 5;
const int nValue
nValue2
2(7);
// initialize explicitly
// initialize implictly
const Date cDate
cDate;;
// initialize using default constructor
const Date cDate2
cDate2(10
10,, 16
16,, 2020
2020);
); // initialize using parameterized constructor
23
Const class objects and member functions
class Something
{
public:
int m_nValue;
Something() { m_nValue = 0; }
void ResetValue() { m_nValue = 0; }
void SetValue(int nValue) { m_nValue = nValue; }
int GetValue() { return m_nValue; }
};
int main()
{
const Something cSomething;
// calls default constructor
cSomething.m_nValue
m_nValue = 5;
// Error: violates const
cSomething.ResetValue
ResetValue();
// Error: violates const
cSomething.SetValue
SetValue(5);
// Error: violates const
std::cout << cSomething.GetValue
GetValue();
// Error !!!
}
24
Const class objects and member functions
• const class objects can only call const member functions
functions,
GetValue() has not been marked as a const member function.
A const member function is a member function that
guarantees it will not change any class variables or call any
non--const member functions
non
class Something {
int m_nValue;
public:
/*…*/
int GetValue() const { return m_nValue; }
};
int main() {
const Something cSomething;
std::cout << cSomething.GetValue
GetValue(); // Ok
}
25
Const class objects and member functions
class Something {
int m_nValue;
public:
/*…*/
int GetValue() const;
};
int Something ::GetValue() const
{
return m_nValue;
}
int main() {
const Something cSomething;
std::cout << cSomething.GetValue
GetValue(); // Ok
}
26
Const class objects and member functions
• Note that constructors should not be marked as const
const. This is
because const objects should initialize their member
variables, and a const constructor would not be able to do so
• ResetValue() has been marked as a const member function,
but it attempts to change m_nValue. This will cause a
compiler error
class Something {
int m_nValue;
public:
/*…*/
void ResetValue () const {m_nValue = 0 ; }
};
27
Const class objects and member functions
• although it is not done very often, it is possible to overload a
function in such a way to have a const and non
non--const version
of the same function
class Something
{
int m_nValue;
public:
Something(){}
const int
int&
& GetValue() const { return m_nValue; }
int&
int
& GetValue() { return m_nValue; }
};
Something cSomething;
cSomething.GetValue(); // calls nonnon-const GetValue
GetValue();
();
const Something cSomething2;
cSomething2.GetValue(); // calls const GetValue
GetValue();
();
28
Static member variables
• file scope and the static keyword
int GenerateID()
{
static int s_nID = 0;
return s_nID++;
}
int main()
{
std::cout << GenerateID
GenerateID()
() << std::endl; // 0
std::cout << GenerateID
GenerateID()
() << std::endl; // 1
std::cout << GenerateID
GenerateID()
() << std::endl; // 2
return 0;
}
29
Static member variables
class Something
{
private:
int m_nValue;
public:
Something(int value) {
m_nValue = value;
}
int GetValue() {return m_nValue;}
};
int main() {
Something cFirst(1);
Something cSecond(2);
cout <<
<<cFirst.GetValue
cFirst.GetValue()
()
<< cSecond.GetValue
cSecond.GetValue();
(); // 12
return 0;
}
class Something
{
public:
static int s_nValue
s_nValue;;
};
int Something::
Something::s_nValue
s_nValue = 1;
int main()
{
Something cFirst;
cFirst.s_nValue = 2;
Something cSecond;
cout << cSecond.s_nValue
<< cFirst.s_nValue
cFirst.s_nValue; // 22
return 0;
}
30
Static member variables
• Although you can access static members through objects of
the class type:
– this is somewhat misleading. cFirst
cFirst..s_nValue implies that s_nValue
belongs to cFirst
cFirst,, and this is really not the case
– s_nValue does not belong to any object. In fact, s_nValue exists even
if there are no objects of the class have been instantiated!
• it is better to think of static members as belonging to the
class itself, not the objects of the class
class. Because s_nValue
exists independently of any class objects, it can be accessed
directly using the class name and the scope operator:
class Something {
public:
static int s_nValue
s_nValue;;
};
int main() {
Something::
Something
::s_nValue
s_nValue = 2;
std::cout << Something::
Something::s_nValue
s_nValue;
}
31
An example of Static member variables
class Something {
private:
static int s_nIDGenerator;
int m_nID;
public:
Something() { m_nID = s_nIDGenerator
s_nIDGenerator++;
++; }
int GetID() const { return m_nID; }
};
int Something::s_nIDGenerator = 1;
int main() {
Something cFirst;
Something cSecond;
Something cThird;
cout << cFirst.GetID
cFirst.GetID()
() << endl;
cout << cSecond.GetID
cSecond.GetID()
() << endl;
cout << cThird.GetID
cThird.GetID()
() << endl;
return 0;
}
32
Class in UML Class Diagrams
• A class in an object oriented system provides a
crisp abstraction of a well defined set of
responsibilities
ClassName
vis attribute : type
• vis = visibility
vis operation
operation((arg list
list)) : return type
– + denotes public attributes or operations
– - denotes private attributes or operations
– # denotes protected attributes or operations
• attribute = data member (aka field
field)
• operation = method (or constructor
constructor)
• static members are indicated by underlining
33
Class in UML Class Diagrams
34
Exercises
• writing a singleton class
• Design a class diagram for modeling relationship
between student and their current semester
courses
– Noun/verb analysis (Grammatical Parsing)
• Look for nouns or noun phrases - these are candidate
classes or attributes
• Look for verbs or verb phrases - these are candidate
responsibilities or operations (Information expert
expert))
35
Friend functions
class Accumulator
{
private:
int m_nValue;
public:
Accumulator() { m_nValue = 0; }
void Add(int nValue) { m_nValue += nValue; }
// Make the Reset() function a friend of this class
friend void Reset(Accumulator &cAccumulator);
};
// Reset() does not have a *this pointer
// Reset() is now a friend of the Accumulator class
void Reset(Accumulator
Accumulator &cAccumulator
&cAccumulator)
{
// can access the private data of Accumulator objects
cAccumulator.m_nValue = 0;
}
36
Friend functions
// A function can be a friend of more than one class
class Humidity;
class Temperature {
int m_nTemp;
public:
Temperature(int nTemp) { m_nTemp = nTemp; }
friend void PrintWeather
PrintWeather(Temperature &cTemperature, Humidity &cHumidity);
};
class Humidity {
int m_nHumidity;
public:
Humidity(int nHumidity) { m_nHumidity = nHumidity; }
friend void PrintWeather
PrintWeather(Temperature &cTemperature, Humidity &cHumidity);
};
void PrintWeather
PrintWeather(Temperature &cTemperature, Humidity &cHumidity) {
std::cout << cTemperature.m_nTemp << cHumidity.m_nHumidity << std::endl;
}
37
Friend classes
class Display;
class Storage {
Storage cStorage(5, 6.7);
int m_nValue;
Display cDisplay();
double m_dValue;
public:
cDisplay.DisplayItem(cStorage);
Storage(int nValue, double dValue) {
m_nValue = nValue; m_dValue = dValue;
}
// Make the Display class a friend of Storage
friend class Display;
};
class Display { /* … */
public:
Display() { }
void DisplayItem(Storage &cStorage) {
cout << cStorage.m_nValue << " " << cStorage.m_dValue
cStorage.m_dValue;
}
};
38
Friend classes
class Display;
Storage
class Storage {
- m_dValue
m_dValue::: double
int m_nValue;
- m_nValue : int
double m_dValue;
+ Storage(int
Storage(int nValue
nValue,, double dValue
dValue))
public:
Storage(int nValue, double dValue) {
Dependency is a weaker form of
m_nValue = nValue;
relationship which indicates that one
m_dValue = dValue;
class depends on another because it
}
uses it at some point in time. One class
depends on another if the independent
friend class Display;
class is a parameter variable or local
};
variable of a method of the dependent
class Display { /* … */
class
public:
Display
Display() { }
void DisplayItem(Storage
Storage &cStorage
&cStorage) {
cout << cStorage.m_nValue << " "
+ Display()
<< cStorage.m_dValue;
+ DisplayItem(Storage
DisplayItem(Storage &cStorage
&cStorage)) : void
}
39
};
Anonymous variables and objects
int Add(int nX, int nY)
{
int nSum = nX + nY;
return nSum
nSum;;
}
// An anonymous variable is a variable that is given no name
int AnonymousAdd(int nX, int nY)
{
return nX + nY
nY;;
}
int main()
{
cout << Add(
Add(5
5, 3);
cout << AnonymousAdd (5, 3);
}
40
Anonymous variables and objects
class Cents{
int m_nCents;
public:
Cents(int nCents) { m_nCents = nCents; }
int GetCents() { return m_nCents; }
};
Cents Add(Cents &c1, Cents &c2) {
Cents cTemp(c1.GetCents() + c2.GetCents());
return cTemp
cTemp;;
}
int main() {
Cents cCents1(6);
Cents cCents2(8);
Cents cCentsSum = Add(cCents
Add(cCents1
1, cCents2
cCents2);
cout << "I have " << cCentsSum.GetCents
cCentsSum.GetCents()
() << " cents.";
}
41
Anonymous variables and objects
class Cents
Cents cCents(5); // normal variable
{
// initialize it and then destroy it
int m_nCents;
Cents(7
Cents(
7); // anonymous variable
public:
Cents(10
Cents(
10).
).GetCents
GetCents()
();; // 10
Cents(int nCents) { m_nCents = nCents; }
int GetCents() { return m_nCents; }
};
Cents Add(Cents &c1, Cents &c2)
{
return Cents
Cents(c1.GetCents() + c2.GetCents());
}
int main()
{
Cents cCents1(6);
Cents cCents2(8);
cout << "I have " << Add(cCents
Add(cCents1
1, cCents2
cCents2).
).GetCents
GetCents()
() << " cents.";
}
42
Overloading the arithmetic operators
• Operator overloading allows the programmer to
define how operators (such
such as +, -, ==, =, and !)
should interact with various data types
• Operators as functions
– When you see the expression nX + nY
nY, you can
translate this in your head to operator+(
operator+(nX
nX,, nY
nY))
– function overloading is used to resolve the function
calls to different versions of the function
• you can only overload the operators that exist
– You can not create new operators
• e.g., you could not create an operator ** to do exponents
43
Overloading the arithmetic operators
class Cents {
int m_nCents;
public:
Cents(int nCents) { m_nCents = nCents; }
// Add Cents + Cents
friend Cents operator+
operator+(const Cents &c1, const Cents &c2);
int GetCents() { return m_nCents; }
};
// note: this function is not a member function!
Cents operator+
operator+(const Cents &c1, const Cents &c2) {
// use the Cents constructor and operator+(int
operator+(int,, int
int))
return Cents(c1.m_nCents + c2.m_nCents);
}
int main() {
Cents cCents1(6);
(cCents
cCents1
1 + cCents2
cCents2) .GetCents
GetCents();
();
Cents cCents2(8);
Cents cCentsSum = cCents
cCents1
1 + cCents2
cCents2;
std::cout << "I have " << cCentsSum .GetCents
GetCents()
() << " cents." ;
}
44
Overloading the arithmetic operators
class Cents {
int m_nCents;
public:
Cents(int nCents) { m_nCents = nCents; }
// Add Cents + Cents
friend Cents operator+
operator+(const Cents &c1, const Cents &c2);
int GetCents() { return m_nCents; }
When the operator does
};
not modify its operands
operands,
// note: this function is not a member function!
operator can overloading
Cents operator+
operator+(const Cents &c1, const Cents &c2) {
friend
function.
function
// use the Cents constructor and operator+(int
operator+(int,, int
int)) via
Overloading operators
return Cents(c1.m_nCents + c2.m_nCents);
using member functions
}
is better option
int main() {
Cents cCents1(6);
Cents cCents2(8);
Cents cCentsSum = cCents
cCents1
1 + cCents2
cCents2;
std::cout << "I have " << cCentsSum .GetCents
GetCents()
() << " cents." ;
45
}
Overloading the arithmetic operators
class Cents {
int m_nCents;
public:
Cents(int nCents) { m_nCents = nCents; }
friend Cents operator+
operator+(const Cents &c1, const Cents &c2);
friend Cents operator
operator--(const Cents &c1, const Cents &c2);
int GetCents() { return m_nCents; }
};
// note: this function is not a member function!
Cents operator+
operator+(const Cents &c1, const Cents &c2) {
// use the Cents constructor and operator+(int
operator+(int,, int
int))
return Cents(c1.m_nCents + c2.m_nCents);
}
// note: this function is not a member function!
Cents operator
operator--(const Cents &c1, const Cents &c2) {
// use the Cents constructor and operatoroperator-(int
int,, int
int))
return Cents(c1.m_nCents - c2.m_nCents);
}
Overloading the
multiplication operator
and division operator are
as easy as defining :
• operator*
• operator/
46
Overloading operators for operands of different types
class Cents {
int m_nCents;
public:
Cents(int nCents) { m_nCents = nCents; }
// Overload cCents + int
friend Cents operator+(const Cents &cCents, int nCents
nCents);
// Overload int + cCents
friend Cents operator+(int
int nCents
nCents, const Cents &cCents);
int GetCents() { return m_nCents; }
Cents c1
c1 = Cents(4
Cents(4) + 6;
};
Cents c2
c2 = 6 + Cents(4
Cents(4);
// note: this function is not a member function!
Cents operator+(const Cents &cCents, int nCents
nCents) {
return Cents(cCents.m_nCents + nCents);
}
// note: this function is not a member function!
Cents operator+(int
int nCents
nCents, const Cents &cCents)
{
return Cents(cCents.m_nCents + nCents);
47
}
Overloading the I/O operators
class Point {
double m_dX, m_dY, m_dZ;
public:
Point(double dX=0.0, double dY=0.0, double dZ=0.0) {
m_dX = dX;
m_dY = dY;
m_dZ = dZ;
}
double GetX() { return m_dX; }
Point cPoint
cPoint((5.0, 6.0, 7.0);
double GetY() { return m_dY; }
cout << cPoint
cPoint;;
double GetZ() { return m_dZ; }
};
int main() {
Point cPoint(5.0, 6.0, 7.0);
cout << "(" << cPoint.GetX() << ", " <<
cPoint.GetY() << ", " <<
cPoint.GetZ() << ")";
}
48
Overloading the I/O operators
class Point {
double m_dX, m_dY, m_dZ;
public:
Point(double dX=0.0, double dY=0.0, double dZ=0.0) {
m_dX = dX;
Point cPoint1(2.0, 3.0, 4.0);
m_dY = dY;
Point cPoint2(6.0, 7.0, 8.0);
m_dZ = dZ;
cout << cPoint1
cPoint1 << " " << cPoint2
cPoint2 << endl
endl;;
}
friend ostream
ostream&
& operator<< (ostream
(ostream &out, Point &cPoint
&cPoint);
);
double GetX() { return m_dX; }
double GetY() { return m_dY; }
double GetZ() { return m_dZ; }
};
ostream&
ostream
& operator<< (ostream
(ostream &out, Point &cPoint
&cPoint)) {
out << "(" << cPoint.m_dX << ", " <<
cPoint.m_dY << ", " <<
cPoint.m_dZ << ")";
return out;
49
}
Overloading the I/O operators
class Point {
double m_dX, m_dY, m_dZ;
public:
Point(double dX=0.0, double dY=0.0, double dZ=0.0) {
m_dX = dX;
m_dY = dY;
m_dZ = dZ;
}
friend istream
istream&
& operator>> (istream
istream &in, Point &cPoint);
&
double GetX() { return m_dX; }
double GetY() { return m_dY; }
Point cPoint;
double GetZ() { return m_dZ; }
cin >> cPoint
cPoint;;
};
istream&
istream
& operator>> (istream
istream &in, Point &cPoint)
&
{ Point cPoint1, cPoint2;
in >> cPoint.m_dX;
cin >> cPoint1
cPoint1 >> cPoint2
cPoint2;
in >> cPoint.m_dY;
in >> cPoint.m_dZ;
return in;
50
}
Overloading the comparison operators
class Point {
double m_dX, m_dY, m_dZ;
public:
Point(double dX=0.0, double dY=0.0, double dZ=0.0) { /* … */ }
friend bool operator== (Point &cP1, Point &cP2);
friend bool operator!= (Point &cP1, Point &cP2);
friend bool operator> (Point &cP1, Point &cP2);
friend bool operator<= (Point &cP1, Point &cP2);
friend bool operator< (Point &cP1, Point &cP2);
friend bool operator>= (Point &cP1, Point &cP2); /* … */
};
bool operator== (Point &cP1, Point &cP2) {
return (cP1.m_dX == cP2.m_dX && cP1.m_dY == cP2.m_dY &&
cP1.m_dZ == cP2.m_dZ);
}
bool operator!= (Point &cP1, Point &cP2) { return !(cP1 == cP2); }
/* … */
51
Overloading unary operators
// positive (+), negative ((-) and logical not (!)
class Point {
double m_dX, m_dY, m_dZ;
public:
Point(double dX=0.0, double dY=0.0, double dZ=0.0) { /* … */ }
friend Point operator
operator-- (const Point &cPoint);
friend bool operator! (const Point &cPoint);
/* … */
};
Point operator
operator-- (const Point &cPoint) {
return Point(-cPoint.m_dX, -cPoint.m_dY, -cPoint.m_dZ);
}
bool operator! (const Point &cPoint){
return (cPoint.m_dX == 0.0 && cPoint.m_dY == 0.0 && cPoint.m_dZ == 0.0);
}
Point cPoint; // use default contructor to set to (0
(0.0, 0.0, 0.0)
if (!cPoint
(!cPoint)) …
52
Overloading operators using member functions
• When overloading an operator using a member
function:
– The leftmost operand of the overloaded operator must be
an object of the class type
– The leftmost operand becomes the implicit *this
parameter. All other operands become function
parameter
parameters
• Most operators can actually be overloaded either way,
however there are a few exception cases:
– If the leftmost operand is not a member of the class type
• e.g., operator+(int
operator+(int,, YourClass
YourClass)), operator<<(
operator<<(ostream
ostream&,
&, YourClass
YourClass))
– The assignment (=),
(=) subscript ([]),
([]) call (()),
(()) and member
selection ((->) operators must be overloaded as member
functions
53
Overloading operators using member functions
class Cents
{
private:
int m_nCents;
public:
Cents(int nCents) { m_nCents = nCents; }
friend Cents operator-(const Cents &cCents);
friend Cents operator+(Cents &cCents, int nCents);
};
// note: this function is not a member function!
Cents operator-(const Cents &cCents) {
return Cents(-cCents.m_nCents);
}
Cents operator+(Cents &cCents, int nCents) {
return Cents(cCents.m_nCents + nCents);
}
54
Overloading operators using member functions
class Cents
Remember that C++ compiler internally converts
{
Cents Cents
Cents::operator
::operator--(); to
Cents operatoroperator-(const Cents *this),
*this)
private:
which you will note is almost identical to our friend
int m_nCents;
version Cents operator-(const Cents &cCents)!
public:
Cents(int nCents) { m_nCents = nCents; }
// Overload -cCents : -this
Cents operator-();
// Overload cCents + int : this + int
Cents operator+(int nCents);
};
// note: the following functions are member function!
Cents Cents
Cents::operator
::operator--() {
return Cents(-m_nCents);
}
Cents Cents
Cents::operator+
::operator+(int nCents) {
return Cents(m_nCents + nCents); // this -> m_nCents + …
}
55
Overloading prefix increment and decrement
// overloaded exactly the same as any normal unary operator
class Digit {
int m_nDigit;
public:
Digit(int nDigit=0) { m_nDigit = nDigit; }
Digit& operator++();
Digit d;
Digit& operator--();
cout<< (++++d).GetDigit
(++++d).GetDigit();
(); // 2
int GetDigit() const { return m_nDigit; }
};
Digit& Digit::operator++
Digit::operator++() {
if (m_nDigit == 9) m_nDigit = 0; else ++m_nDigit;
return *this; // Note that we return *this
}
Digit& Digit::operator
Digit::operator---() {
if (m_nDigit == 0) m_nDigit = 9; else --m_nDigit;
return *this; // Note that we return *this
}
56
Overloading postfix increment and decrement
// uses a “dummy variable” or “dummy argument” for the postfix operators
class Digit {
Digit cDigit(5);
int m_nDigit;
++cDigit
++
cDigit;; // calls Digit::operator++();
public:
cDigit++;
++; // calls Digit::operator++(int
Digit::operator++(int);
);
Digit(int nDigit=0) { m_nDigit = nDigit; } cDigit
Digit operator++
operator++(int
int); // postfix
Digit operator
operator---(int
int);
// postfix
int GetDigit() const { return m_nDigit; }
};
Digit Digit
Digit::operator++
::operator++(int
int) {
Digit cResult(m_nDigit
m_nDigit); // a temporary variable with our current digit
++(*this);
// Use prefix operator to increment this digit
return cResult;
// return temporary result
}
Digit Digit
Digit::operator
::operator---(int
int) {
Digit cResult(m_nDigit
m_nDigit);
--(*this);
-(*this);
return cResult;
}
57
Overloading the subscript operator
class IntList
{
private:
int m_anList[10];
public:
void SetItem(int nIndex, int nData) { m_anList[nIndex] = nData; }
int GetItem(int nIndex) { return m_anList[nIndex]; }
};
int main()
{
IntList cMyList;
cMyList.SetItem((2, 3);
cMyList.SetItem
return 0;
}
58
Overloading the subscript operator
class IntList
{
private:
int m_anList[10];
Why operator[] returns a reference?
public:
int&
int
& operator[] (const
const int nIndex);
};
int&
int
& IntList
IntList::
::operator[]
operator[] (const
const int nIndex)
{
return m_anList[nIndex];
}
int main() {
IntList cMyList;
cMyList[[2] = 3;
cMyList
// set a value
cout << cMyList
cMyList[[2];
// get a value
}
59
Overloading the subscript operator
#include <cassert> // for assert()
class IntList
{
private:
int m_anList[10];
int anArray[5];
anArray[7] = 3; // index 7 is out of bounds!
public:
int&
int
& operator[] (const
const int nIndex);
};
int& IntList
int&
IntList::
::operator[]
operator[] (const
const int nIndex)
{
assert(nIndex
assert(
nIndex >= 0 && nIndex < 10
10);
);
return m_anList[nIndex];
}
60
Overloading the parenthesis operator
#include <cassert> // for assert()
Matrix cMatrix;
class Matrix {
cMatrix((1, 2) = 4.5;
cMatrix
double adData[4][4];
std::cout << cMatrix
cMatrix((1, 2); // 4.5
public:
cMatrix();
cMatrix
();
// erase cMatrix
std::cout << cMatrix
cMatrix((1, 2); // 0
Matrix() {
for (int nCol=0; nCol<4; nCol++)
for (int nRow=0; nRow<4; nRow++) adData[nRow][nCol] = 0.0;
}
double& operator()
operator()(const int nCol, const int nRow);
void operator()
operator()();
};
double& Matrix::operator()
Matrix::operator()(const int nCol, const int nRow) {
assert(nCol
assert(
nCol >= 0 && nCol < 4); assert(nRow
assert(nRow >= 0 && nRow < 4);
return adData[nRow][nCol];
}
void Matrix::operator()()
Matrix::operator()(){
for (int nCol=0; nCol<4; nCol++)
for (int nRow=0; nRow<4; nRow++) adData[nRow][nCol] = 0.0;
61
}
Overloading typecasts
class Cents {
int m_nCents;
public:
Cents(int nCents=0) {
m_nCents = nCents;
}
int GetCents() { return m_nCents; }
void SetCents(int nCents) { m_nCents = nCents; }
};
int nValue = 5;
void PrintInt(int nValue)
// int implicitly cast to a double
{
double dValue = nValue;
cout << nValue;
// our goal
Cents cCents(7);
}
PrintInt(cCents
cCents); // print 7
int main()
{
int nCents = static_cast
static_cast<<int
int>(
>(cCents
cCents);
);
Cents cCents(7);
PrintInt(nCents); // print 7
PrintInt(cCents.GetCents
cCents.GetCents()
());
}
62
Overloading typecasts
/* Casting operators do not have a return type. C++ assumes you will be
returning the correct type */
class Cents {
Cents cCents(7);
private:
PrintInt((cCents
PrintInt
cCents);
); // print 7
int m_nCents;
int nCents = static_cast
static_cast<<int
int>(
>(cCents
cCents);
);
public:
PrintInt(nCents); // print 7
Cents(int nCents=0) {
m_nCents = nCents;
}
// Overloaded int cast
operator int() { return m_nCents; }
int GetCents() { return m_nCents; }
void SetCents(int nCents) { m_nCents = nCents; }
};
void PrintInt(int
int nValue) {
cout << nValue;
}
63
Overloading typecasts
class Cents {
int m_nCents;
public:
Dollars cDollars(9);
/* … */
// cDollars will be cast to a Cents
int GetCents() { return m_nCents; }
PrintCents(cDollars
cDollars);
};
/////////////////////////////////////
class Dollars {
int m_nDollars;
public:
Dollars(int nDollars=0) { m_nDollars = nDollars; }
// Allow us to convert Dollars into Cents
operator Cents
Cents() { return Cents(m_nDollars * 100); }
};
void PrintCents
PrintCents((Cents cCents
cCents))
{
cout << cCents.GetCents();
}
64
copy constructor and overloading assignment operator
class Cents
Cents cMark(5);
// calls Cents constructor
{
Cents cNancy;
// calls Cents default constructor
int m_nCents;
cNancy = cMark; // calls Cents assignment operator
public:
Cents(int
int nCents
nCents==0) { // note the following line
m_nCents = nCents; Cents cNancy = cMark; // calls Cents copy constructor!
}
// Copy constructor
Cents(const
const Cents &cSource)
&
{
m_nCents = cSource.m_nCents;
}
Cents& operator= (const Cents &cSource);
};
Cents& Cents::operator= (const Cents &cSource) {
// do the copy
m_nCents = cSource.m_nCents;
// return the existing object
return *this; // cMark = cNancy = cFred = cJoe
cJoe;;
65
}
copy constructor and overloading assignment operator
• The assignment operator is used to copy the values
from one object to another already existing object. The
key words here are already existing
– Cents cMark(5);
– Cents cNancy;
– cNancy = cMark;
// calls Cents constructor
// calls Cents default constructor
// calls Cents assignment operator
• A copy constructor is a special constructor that
initializes a new object from an existing object
– Cents cNancy = cMark; // calls Cents copy constructor!
– Because the second statement uses an equals symbol in
it, you might expect that it calls the assignment operator
operator..
However, it doesn’t! It actually calls a special type of
constructor called a copy constructor
66
copy constructor and overloading assignment operator
• It is possible in C++ to do a self
self--assignment
assignment:
– cMark = cMark; // valid assignment
• to do a check for self
self--assignment at the top
of an overloaded assignment operator
Cents& Cents::operator= (const Cents &cSource) {
// check for selfself-assignment by comparing the address of the
if (this == &cSource
&cSource)) return *this;
m_nCents = cSource.m_nCents;
return *this;
}
67
copy constructor and overloading assignment operator
• Just like default constructor
constructor, C++ will provide
a default copy constructor if you do not provide
one yourself. However, unlike other operators,
C++ will provide a default assignment operator if
you do not provide one yourself!
• Because C++ does not know much about your
class, the default copy constructor and default
assignment operators it provides are very
simple. They use a copying method known as a
simple
memberwise copy (also known as a shallow
copy).
copy
68
Shallow copying
• A shallow copy means that C++ copies each
member of the class individually using the
assignment operator
operator. When classes are
simple (eg. do not contain any dynamically
allocated memory), this works very well:
class Cents
{
private:
int m_nCents;
public:
Cents(int nCents=0)
{
m_nCents = nCents;
}
};
69
Shallow copying
class MyString
{
char *m_pchString;
*
int m_nLength;
MyString cHello("Hello, world!");
{
MyString cCopy = cHello; // use default copy constructor
} // cCopy goes out of scope here
std::cout << cHello.GetString(); // this will crash
public:
MyString(char *pchString="") // default constructor
{
// Find the length of the string plus one character for a terminator
m_nLength = strlen(pchString) + 1;
m_pchString= new char[m_nLength
char[m_nLength];
]; // Allocate a buffer equal to this length
strncpy(m_pchString, pchString, m_nLength);
m_pchString[m_nLength-1] = '\0';
// Make sure the string is terminated
}
~MyString() {
// destructor
delete[] m_pchString
m_pchString;; // We need to deallocate our buffer
m_pchString = 0;
// Set m_pchString to null just in case
}
char* GetString() { return m_pchString; }
int GetLength() { return m_nLength; }
};
70
Shallow copying
• This (MyString
MyString cCopy = cHello
cHello;;) seems harmless
enough as well, but it’s actually the source of our
problem!
• When this line is evaluated, C++ will use the default
copy constructor
– because we haven’t provided our own
– Because a shallow pointer copy just copies the address of
the pointer
– the address of cHello
cHello..m_pchString is copied into
cCopy..m_pchString
cCopy
m_pchString. As a result, cCopy.m_pchString and
cHello.m_pchString are now both pointing to the same
piece of memory!
71
Shallow copying
• consider the following line
– } // cCopy goes out of scope here
• When cCopy goes out of scope
scope, the MyString
destructor is called on cCopy
– The destructor deletes the dynamically allocated
memory that both cCopy
cCopy..m_pchString and
cHello..m_pchString are pointing to!
cHello
– Consequently, by deleting cCopy, we’ve
(inadvertently) affected cHello. Note that
destructor will set cCopy.m_pchString to 0,
cHello..m_pchString will be left pointing to
cHello
deleted (invalid) memory!
also
the
but
the
72
Deep copying
class MyString {
/ * … */
public:
// Copy constructor
MyString(const MyString
MyString&
& cSource) {
// because m_nLength is not a pointer, we can shallow copy it
m_nLength = cSource.m_nLength;
// m_pchString is a pointer, so we need to deep copy it if it is nonnon-null
if (cSource.m_pchString
(cSource.m_pchString)) {
// allocate memory for our copy
m_pchString = new char[m_nLength
char[m_nLength]];
// Copy the string into our newly allocated memory
strncpy(m_pchString, cSource.m_pchString, m_nLength);
}
else
MyString cHello("Hello, world!");
m_pchString = 0; {
}
MyString cCopy = cHello; // use copy constructor
};
} // cCopy goes out of scope here
std::cout << cHello.GetString();
73
Deep copying
• What happens when we do the following?
– MyString cHello
cHello("Hello,
("Hello, world!");
– cHello = cHello
cHello;; // calls assignment operator
// Problematic assignment operator
MyString& MyString
MyString::operator=
::operator=(const MyString& cSource) {
// Note: No check for selfself-assignment!
delete[] m_pchString; // deallocate any value that this string is holding!
// because m_nLength is not a pointer, we can shallow copy it
m_nLength = cSource.m_nLength;
if (cSource.m_pchString) {
m_pchString = new char[m_nLength
char[m_nLength];
]; // allocate memory for our copy
strncpy(m_pchString, cSource.m_pchString, m_nLength);
}
else
m_pchString = 0;
return *this;
}
74
Deep copying
• What happens when we do the following?
– MyString cHello
cHello("Hello,
("Hello, world!");
– cHello = cHello
cHello;; // calls assignment operator
// Assignment operator
MyString& MyString
MyString::operator=
::operator=(const MyString& cSource) {
if (this == &cSource
&cSource) return *this; // check for selfself-assignment
delete[] m_pchString;
// deallocate any value that this string is holding!
// because m_nLength is not a pointer, we can shallow copy it
m_nLength = cSource.m_nLength;
// now we need to deep copy m_pchString
if (cSource.m_pchString) {
m_pchString = new char[m_nLength]; // allocate memory for our copy
strncpy(m_pchString, cSource.m_pchString, m_nLength);
}
else
m_pchString = 0;
return *this;
}
75
Preventing copying
• C++ will not automatically create a default
copy constructor and default assignment
operator, because we’ve told the compiler
we’re defining our own functions
class MyString
{
private:
char *m_pchString;
int m_nLength;
MyString(const MyString
MyString(const
MyString&
& cSource
cSource);
);
MyString&
MyString
& operator=
operator=(const
(const MyString
MyString&
& cSource
cSource);
);
public:
/*…*/
};
76
Constructor initialization lists
class Something
{
private:
int m_nValue;
double m_dValue;
int *m_pnValue;
class Something
{
private:
int m_nValue;
double m_dValue;
int *m_pnValue;
public:
Something()
{
m_nValue = 0;
m_dValue = 0.0;
m_pnValue = 0;
}
};
public:
Something() : m_nValue
m_nValue((0), m_dValue
m_dValue((0.0), m_pnValue
m_pnValue((0)
{
}
};
77
Constructor initialization lists
class Something
{
private:
const int m_nValue;
public:
Something()
{
m_nValue = 5;
}
};
class Something
{
private:
const int m_nValue;
public:
Something() : m_nValue
m_nValue((5)
{
}
};
78
Aggregation
• A special form of association that specifies a
whole--part relationship between the
whole
aggregate (whole) and a component part
• no ownership between the complex object
and the subobjects is implied
• When an aggregate is destroyed
destroyed, the
subobjects are not destroyed
79
Constructor initialization lists
class Teacher {
private:
string m_strName;
public:
Teacher(string strName) : m_strName(strName)
{
}
string GetName() { return m_strName; }
};
////////////////////////////////////////////////////////////
class Department {
private:
Teacher *m_pcTeacher; // This dept holds only one teacher
public:
Department(Teacher *pcTeacher=NULL)
: m_pcTeacher(pcTeacher)
{
}
};
80
Aggregation
class Teacher {
// Create a teacher outside the scope of the Department
private:
Teacher *pTeacher = new Teacher("Bob"); // create a teacher
string m_strName;
{
public:
Department cDept(pTeacher);
Teacher(string strName) : m_strName(strName)
} // cDept goes out of scope here and is destroyed
{
}
pTeacher still
string GetName() { return //
m_strName;
} exists here because cDept did not destroy it
delete pTeacher;
};
////////////////////////////////////////////////////////////
class Department {
private:
Teacher *m_pcTeacher; // This dept holds only one teacher
public:
Department(Teacher *pcTeacher=NULL)
: m_pcTeacher(pcTeacher)
{
}
};
81
Composition
• composition:
composition: building complex objects from
simpler objects
– you have a head, a body, some legs, arms, and …
• A form of aggregation with strong ownership
and coincident lifetime as part of the whole
– A personal computer has
has--a CPU, a motherboard, and
other components
• has a strong life cycle dependency
– composition is destroyed
destroyed, all of the subobjects are
destroyed as well
82
Composition
#include "CPU.h
"CPU.h""
#include "Motherboard.h
"Motherboard.h""
#include "RAM.h
"RAM.h""
class PC
{
private:
CPU m_cCPU
m_cCPU;;
Motherboard m_cMotherboard
m_cMotherboard;;
RAM m_cRAM
m_cRAM;;
};
PC::PersonalComputer( int nCPUSpeed, char *strMotherboardModel, int nRAMSize)
: m_cCPU
m_cCPU((nCPUSpeed
nCPUSpeed),
), m_cMotherboard
m_cMotherboard((strMotherboardModel
strMotherboardModel),
), m_cRAM
m_cRAM((nRAMSize
nRAMSize))
{
}
83
Relationships
84
Introduction to inheritance
• Inheritance involves creating new objects by
directly acquiring the attributes and behaviors
of other objects and then extending or
specializing them
• an is
is--a relationship between two classes
– C++ inherited many features from C
Fruit
Shape
parent or base
Rectangle
Apple
Banana
Triangle
child or derived
85
Liskov substitution principle
• introduced by Barbara Liskov in a 1987
– Substitutability is a principle in object-oriented
programming
• if S is a subtype of T, then objects of type T may
be replaced with objects of type S without
altering any of the desirable properties of that
program
– Preconditions cannot be strengthened in a subtype
– Postconditions cannot be weakened in a subtype
– Invariants of the supertype must be preserved in a
subtype
86
Inheritance
class Person {
public:
string m_strName;
int m_nAge;
bool m_bIsMale;
string GetName() { return m_strName; }
int GetAge() { return m_nAge; }
bool IsMale() { return m_bIsMale; }
Person(string strName = "", int nAge = 0, bool bIsMale = false)
: m_strName(strName), m_nAge(nAge), m_bIsMale(bIsMale)
{}
};
// BaseballPlayer publicly inheriting Person
class BaseballPlayer : public Person {
public:
double m_dBattingAverage;
int m_nHomeRuns;
BaseballPlayer(double dBattingAverage = 0.0, int nHomeRuns = 0)
: m_dBattingAverage(dBattingAverage), m_nHomeRuns(nHomeRuns)
{ }
};
87
Inheritance
class Person {
public:
// Create a new BaseballPlayer object
string m_strName;
BaseballPlayer cJoe;
int m_nAge;
cJoe.m_strName
m_strName = "Joe";
bool m_bIsMale;
// Print out the name
string GetName() { return m_strName; }
cout << cJoe.GetName
GetName() << std::endl;
int GetAge() { return m_nAge; }
bool IsMale() { return m_bIsMale; }
Person(string strName = "", int nAge = 0, bool bIsMale = false)
: m_strName(strName), m_nAge(nAge), m_bIsMale(bIsMale)
{}
};
// BaseballPlayer publicly inheriting Person
class BaseballPlayer : public Person {
public:
double m_dBattingAverage;
int m_nHomeRuns;
BaseballPlayer(double dBattingAverage = 0.0, int nHomeRuns = 0)
: m_dBattingAverage(dBattingAverage), m_nHomeRuns(nHomeRuns)
{ }
};
88
Inheritance
class Employee
{
public:
string m_strEmployerName;
double m_dHourlySalary;
long m_lEmployeeID;
Employee(string strEmployerName, double dHourlySalary, long lEmployeeID)
: m_strEmployerName(strEmployerName), m_dHourlySalary(dHourlySalary),
m_lEmployeeID(lEmployeeID)
{}
double GetHourlySalary() { return m_dHourlySalary; }
void PrintNameAndSalary() {
cout << m_strName << ": " << m_dHourlySalary << std::endl;
}
};
class Supervisor:: public Employee
{
public:
// This Supervisor can oversee a max of 5 employees
int m_nOverseesIDs[5];
};
89
Constructors and initialization of derived classes
class Base
{
public:
int m_nValue;
class Derived:: public Base
{
public:
double m_dValue;
Base(int nValue=0)
: m_nValue(nValue)
{ }
};
Derived(double dValue=0.0)
: m_dValue(dValue)
{ }
};
int main() {
Base cBase(5);
Derived cDerived(1.3);
}
Memory for cBase is set aside
The appropriate Base constructor is called
The initialization list initializes variables
The body of the constructor executes
Control is returned to the caller
Memory for cDerived is set aside (enough for both the Base and Derived portions).
The appropriate Derived constructor is called
The Base object is constructed first using the appropriate Base constructor
The initialization list initializes variables
The body of the constructor executes
Control is returned to the caller
90
Constructors and initialization of derived classes
class Base {
public:
int m_nValue;
Base(int nValue=0)
: m_nValue(nValue)
{ }
};
class Derived:: public Base {
public:
double m_dValue;
Derived(double dValue=0.0, int nValue=0)
// does not work
: m_dValue(dValue), m_nValue(nValue)
{ }
};
C++ prevents classes from initializing inherited member variables in the initialization
list of a constructor
constructor. Why? The answer has to do with const and reference
variables. Consider what would happen if m_nValue were const. Because const
variables must be initialized with a value at the time of creation, the base class
constructor must set it’s value when the variable is created
created.. However, when the base
class constructor finishes, the derived class constructors initialization lists are then
executed.. Each derived class would then have the opportunity to initialize that
executed
variable, potentially changing it’s value! By restricting the initialization of variables
to the constructor of the class those variables belong to, C++ ensures that all
variables are initialized only once
once. The end result is that the above example does not
work because m_nValue was inherited from Base, and only non-inherited variables
can be changed in the initialization list.
91
Constructors and initialization of derived classes
class Base {
public:
int m_nValue;
Base(int nValue=0)
: m_nValue(nValue)
{ }
};
class Derived:: public Base {
public:
double m_dValue;
Derived(double dValue=0.0, int nValue=0)
// Call Base(int
Base(int)) constructor with value nValue
nValue!!
: Base(nValue
Base(nValue)), m_dValue(dValue)
{ }
};
• When a derived class is destroyed
destroyed, each
destructor is called in the reverse order of
construction
92
Inheritance and access specifiers
class Base {
public:
// can be accessed by anybody
int m_nPublic;
private:
// can only be accessed by Base member functions (but not derived classes)
int m_nPrivate;
protected:
// can be accessed by Base member functions, or derived classes
int m_nProtected;
};
class Derived: public Base {
public:
Derived() {
m_nPublic = 1; // allowed: can access public base members from derived class
m_nPrivate = 2; // not allowed: can not access private base members from derived class
m_nProtected = 3; // allowed: can access protected base members from derived class
}
};
93
Inheritance and access specifiers
class Base {
int main() {
public:
Base cBase;
// can be accessed by anybody
cBase.m_nPublic
m_nPublic = 1; // allowed
int m_nPublic;
cBase.m_nPrivate
m_nPrivate = 2; // not allowed
private:
cBase.m_nProtected
m_nProtected = 3; // not allowed
// can only be accessed by Base member functions (but not derived classes)
}
int m_nPrivate;
protected:
// can be accessed by Base member functions, or derived classes
int m_nProtected;
};
class Derived: public Base {
public:
Derived() {
m_nPublic = 1; // allowed: can access public base members from derived class
m_nPrivate = 2; // not allowed: can not access private base members from derived class
m_nProtected = 3; // allowed: can access protected base members from derived class
}
};
94
Inheritance and access specifiers
// A class can always access it’s own members regardless of access specifier
// Inherit from Base publicly
class Pub:: public Base
{
};
// Inherit from Base privately
class Pri:: private Base
{
};
// Inherit from Base protectedly
class Pro:: protected Base
{
};
class Def:: Base // Defaults to private inheritance
{
};
95
public access specifiers
class Base {
public inheritance is also the easiest to
public:
understand. When you inherit a base class
int m_nPublic;
publicly, all members keep their original
private:
access specifications
int m_nPrivate;
protected:
int main(){
int m_nProtected;
Pub cPub;
};
cPub.m_nPublic
m_nPublic = 1; // ok
// Public inheritance means:
cPub.m_nPrivate
m_nPrivate = 2; // error
// m_nPublic stays public
cPub.m_nProtected
m_nProtected = 3; // error
// m_nPrivate stays private
}
// m_nProtected stays protected
class Pub: public Base {
Pub() {
m_nPublic = 1; // ok: anybody can access public members
m_nPrivate = 2; // Error : derived classes can't access private members in the base class!
m_nProtected = 3; // ok: derived classes can access protected members
}
};
96
private access specifiers
class Base {
With private inheritance, all members
public:
from the base class are inherited as
int m_nPublic;
private. This means private members stay
private:
private, and protected and public
int m_nPrivate;
members become private
protected:
int main(){
int m_nProtected;
Pri cPri;
};
cPri.m_nPublic = 1; // error
// Private inheritance means:
cPri.m_nPrivate = 2; // error
// m_nPublic becomes private
cPri.m_nProtected = 3; // error
// m_nProtected becomes private
}
class Pri : private Base {
Pri() {
m_nPublic = 1; // ok: anybody can access public members
m_nPrivate = 2; // Error : derived classes can't access private members in the base class!
m_nProtected = 3; // ok: derived classes can access protected members
}
};
97
protected access specifiers
class Base {
With protected inheritance, the public and
public:
protected members become protected,
int m_nPublic;
and private members stay private
private:
int m_nPrivate;
int main(){
protected:
Pro cPro;
int m_nProtected;
cPro.m_nPublic
m_nPublic = 1; // error
};
cPro.m_nPrivate
m_nPrivate = 2; // error
// protected inheritance means:
cPro.m_nProtected
m_nProtected = 3; // error
// m_nPublic becomes protected
}
// m_nProtected becomes protected
class Pro : protected Base {
Pro() {
m_nPublic = 1; // ok: anybody can access public members
m_nPrivate = 2; // Error : derived classes can't access private members in the base class!
m_nProtected = 3; // ok: derived classes can access protected members
}
};
98
Adding members in a derived class
class Base {
protected:
int m_nValue;
public:
Base(int nValue)
: m_nValue(nValue)
{ }
Derived cDerived
cDerived(5);
cout << cDerived.GetValue(); // 5
Base cBase
cBase(5);
cout << cBase.GetValue
GetValue(); // Error
cBase.Identify();
// I am a Base
cDerived.Identify(); // I am a Base
void Identify() { cout << "I am a Base"; }
};
class Derived: public Base {
public:
Derived(int nValue)
:Base(nValue)
{ }
int GetValue() { return m_nValue; } // Adding function member
};
99
Redefining functionality in a derived class
class Base {
protected:
int m_nValue;
public:
Base(int nValue) : m_nValue(nValue)
{ }
Derived cDerived(5);
Base cBase(5);
cBase.Identify
Identify();
// I am a Base
cDerived.Identify
Identify(); // I am a Derived
void Identify() { cout << "I am a Base "; }
};
class Derived: public Base {
public:
Derived(int nValue) :Base(nValue) { }
int GetValue() { return m_nValue; } // Adding function member
// Here's our modified function
void Identify() { cout << "I am a Derived"; }
};
100
Adding to existing functionality in a derived class
class Base {
protected:
int m_nValue;
public:
Base(int nValue) : m_nValue(nValue)
{ }
Derived cDerived(5);
Base cBase(5);
cBase.Identify
Identify();
// I am a Base
cDerived.Identify
Identify(); // I am a Base I am a Derived
void Identify() { cout << "I am a Base "; }
};
class Derived: public Base {
public:
Derived(int nValue) :Base(nValue) { }
int GetValue() { return m_nValue; } // Adding function member
// Here's our modified function
void Identify() {
Base::Identify(); // if one write Identify(); then an infinite loop!
cout << "I am a Derived"; }
};
101
Hiding existing functionality in a derived class
class Base {
public:
Base(int nValue) : m_nValue(nValue)
{}
int m_nValue;
protected:
void PrintValue() { cout << m_nValue; }
};
class Derived:: public Base
{
Derived cDerived(7);
/* The following won't work because m_nValue
has been redefined as private */
cout << cDerived.m_nValue
m_nValue; // error
// PrintValue is public in Derived, so this is ok
cDerived.PrintValue
PrintValue(); // prints 7
Base::m_nValue
Base::
m_nValue;;
public:
Derived(int nValue) : Base(nValue)
{}
// Base::PrintValue
Base::PrintValue was inherited as protected, so the public has no access
// But we're changing it to public by declaring it in the public section
Base::PrintValue
Base::
PrintValue;;
};
102
Multiple inheritance
class Scanner
{
};
class Printer
{
};
class Copier:
{
};
public Scanner, public Printer
103
Problems with multiple inheritance
class USBDevice {
long m_lID;
WirelessAdaptor c54G(5442, 181742);
public:
cout << c54G.GetID
GetID(); // Which GetID
GetID()
() do we call?
USBDevice(long lID) : m_lID(lID)
{ }
/* this function call is ambiguous, and you will receive
long GetID() { return m_lID; }
a compiler error if you try to compile it */
};
class NetworkDevice {
cout << c54G.USBDevice::
USBDevice::GetID
GetID();
long m_lID;
public:
NetworkDevice(long lID) : m_lID(lID)
{ }
long GetID() { return m_lID; }
};
class WirelessAdaptor:: public USBDevice
USBDevice,, public NetworkDevice
{
public:
WirelessAdaptor(long lUSBID, long lNetworkID)
: USBDevice
USBDevice((lUSBID
lUSBID),
), NetworkDevice
NetworkDevice((lNetworkID
lNetworkID))
{ }
};
104
diamond problem
/* There are many issues that arise in this context, including whether Copier should
have one or two copies of PoweredDevice
PoweredDevice,, and how to resolve certain types of
ambiguous references */
class PoweredDevice
{
};
class Scanner: public PoweredDevice
{
};
class Printer: public PoweredDevice
{
};
class Copier: public Scanner, public Printer
{
};
Problem : diamond problem
solution : virtual inheritance
105
virtual inheritance
class PoweredDevice {
public:
Copier cCopier(1, 2, 3);
PoweredDevice(int nPower)
PoweredDevice:
PoweredDevice:33
{ cout << "PoweredDevice: " << nPower << endl; }
Scanner:
Scanner:11
};
PoweredDevice:
PoweredDevice:33
class Scanner
Scanner: public PoweredDevice {
Printer:
Printer:22
public:
Scanner(int nScanner, int nPower) : PoweredDevice(nPower)
{ cout << "Scanner: " << nScanner << endl; }
PoweredDevice
PoweredDevice
};
class Printer
Printer: public PoweredDevice {
public:
Scanner
Printer
Printer(int nPrinter, int nPower) : PoweredDevice(nPower)
{ cout << "Printer: " << nPrinter << endl; }
};
Copier
class Copier:: public Scanner, public Printer {
public:
Copier(int nScanner, int nPrinter, int nPower)
: Scanner(
Scanner(nScanner
nScanner,, nPower
nPower),
), Printer(nPrinter
Printer(nPrinter,, nPower
nPower)) { }
};
106
virtual inheritance
class PoweredDevice {
public:
Copier cCopier(1, 2, 3);
PoweredDevice(int nPower)
PoweredDevice:
PoweredDevice:33
{ cout << "PoweredDevice: " << nPower << endl; }
Scanner:
Scanner:11
};
PoweredDevice:
3
Printer: 2
class Scanner: virtual public PoweredDevice {
Printer: 2
public:
Scanner(int nScanner, int nPower) : PoweredDevice(nPower)
{ cout << "Scanner: " << nScanner << endl; }
PoweredDevice
};
class Printer: virtual public PoweredDevice {
public:
Scanner
Printer
Printer(int nPrinter, int nPower) : PoweredDevice(nPower)
{ cout << "Printer: " << nPrinter << endl; }
};
Copier
class Copier:: public Scanner, public Printer {
public: // The Copier constructor is responsible for creating PoweredDevice
Copier(int nScanner, int nPrinter, int nPower)
: Scanner(nScanner, nPower), Printer(nPrinter, nPower), PoweredDevice
PoweredDevice((nPower
nPower)) { }
};
107
single inheritance
• Most of the problems that can be solved using
multiple inheritance can be solved using
single inheritance as well
• Many relatively modern languages such as
Java and C# restricts classes to single
inheritance of normal classes, but allow
multiple inheritance of interface classes
(which we will talk about later)
108
Pointers and references to the base class of derived objects
class Base {
protected:
int m_nValue;
public:
Base(int nValue) : m_nValue(nValue) { }
const char* GetName() { return "Base"; }
int GetValue() { return m_nValue; }
};
class Derived:: public Base {
public:
Derived(int nValue) : Base(nValue) { }
const char* GetName() { return "Derived"; }
int GetValueDoubled() { return m_nValue * 2; }
};
Derived cDerived(5);
cout << "cDerived is a "
<< cDerived.GetName()
<< " and has value " <<
cDerived.GetValue() << endl;
Derived &rDerived
&
= cDerived;
cout << "rDerived is a "
<< rDerived.GetName
.GetName()
<< " and has value "
<< rDerived.GetValue
.GetValue() << endl;
Derived *pDerived
*
= &cDerived;
cout << "pDerived is a “
<< pDerived-->GetName
GetName()
<< " and has value “
<< pDerived-->GetValue
GetValue()
() << endl;
cDerived is a Derived and has value 5
rDerived is a Derived and has value 5
pDerived is a Derived and has value 5
109
Pointers and references to the base class of derived objects
class Base {
protected:
int m_nValue;
Derived cDerived(5);
// These are both legal!
Base &rBase
&
= cDerived;
Base *pBase
*
= &cDerived;
public:
Base(int nValue) : m_nValue(nValue) { }
const char* GetName() { return "Base"; }
int GetValue() { return m_nValue; }
};
class Derived:: public Base {
public:
Derived(int nValue) : Base(nValue) { }
const char* GetName() { return "Derived"; }
int GetValueDoubled() { return m_nValue * 2; }
};
cout << "cDerived is a “
<< cDerived.GetName()
<< " and has value "
<< cDerived.GetValue() << endl;
cout << "rBase is a "
<< rBase.GetName()
<< " and has value "
<< rBase.GetValue() << endl;
cout << "pBase is a "
<< pBase->GetName()
<< " and has value "
<< pBase->GetValue() << endl;
cDerived is a Derived and has value 5
rBase is a Base and has value 5
pBase is a Base and has value 5
110
virtual function
class Base {
protected:
int m_nValue;
Derived cDerived(5);
// These are both legal!
Base &rBase
&
= cDerived;
Base *pBase
*
= &cDerived;
public:
Base(int nValue) : m_nValue(nValue) { }
virtual const char* GetName() { return "Base"; }
int GetValue() { return m_nValue; }
};
class Derived: public Base {
public:
Derived(int nValue) : Base(nValue) { }
virtual const char* GetName(){return "Derived"; }
int GetValueDoubled() { return m_nValue * 2; }
};
cout << "cDerived is a “
<< cDerived.GetName()
<< " and has value "
<< cDerived.GetValue() << endl;
cout << "rBase is a "
<< rBase.GetName()
<< " and has value "
<< rBase.GetValue() << endl;
cout << "pBase is a "
<< pBase->GetName()
<< " and has value "
<< pBase->GetValue() << endl;
cDerived is a Derived and has value 5
rBase is a Derived and has value 5
pBase is a Derived and has value 5
111
virtual function
class A {
public:
virtual const char* GetName() { return "A"; }
};
class B: public A {
public:
virtual const char* GetName() { return "B"; }
};
int main()
{
C cClass;
A &rBase = cClass;
cout << "rBase is a “
<< rBase.GetName() << endl;
return 0;
}
class C: public B {
public:
virtual const char* GetName() { return "C"; }
};
rBase is a C
class D: public C {
public:
virtual const char* GetName() { return "D"; }
};
112
Use of the virtual keyword
class Base {
protected:
int m_nValue;
public:
Base(int nValue) : m_nValue(nValue) { }
virtual const char* GetName() { return "Base"; }
int GetValue() { return m_nValue; }
};
class Derived: public Base {
public:
Derived(int nValue) : Base(nValue) { }
// note lack of virtual keyword
const char* GetName
GetName()
() { return "Derived"; }
int GetValueDoubled() { return m_nValue * 2; }
};
Exactly
the
same
as
if
Derived::GetName() was explicitly
tagged as virtual. Only the most base
class function needs to be tagged as
virtual for all of the derived functions
to work virtually. However, having the
keyword virtual on the derived
functions does not hurt, and it serves
as a useful reminder that the function
is a virtual function rather than a
normal one
one. Consequently, it’s
generally a good idea to use the virtual
keyword for virtualized functions in
derived classes even though it’s not
strictly necessary
113
Return types of virtual functions
class Base {
public:
virtual int GetValue() { return 5; }
};
class Derived: public Base {
public:
virtual double GetValue() { return 6.78; }
};
class Base {
public:
virtual Base* GetThis() { return this; }
};
class Derived: public Base {
public:
virtual Derived* GetThis() { return this; }
};
Under normal circumstances, the
return type of a virtual function and
it’s override must match
match.. Thus, the
following will not work
However, there is one special case in
which this is not true.. If the return
type of a virtual function is a pointer
or a reference to a class, override
functions can return a pointer or a
reference to a derived class
• Note that some older compilers
(eg. Visual Studio 6) do not
support it
114
Example
class Animal {
protected:
string m_strName;
Animal(string strName) : strName(strName) {}
public:
string GetName() { return m_strName; }
virtual const char* Speak() { return "???"; }
};
class Cat: public Animal {
public:
Cat(string strName) : Animal(strName) {}
virtual const char* Speak() { return "Meow"; }
};
class Dog: public Animal {
public:
Dog(string strName): Animal(strName) {}
virtual const char* Speak() { return "Woof"; }
};
We're making this constructor
protected because we don't want
people creating Animal objects
directly, but we still want derived
classes to be able to use it
void Report(Animal &rAnimal) {
cout << rAnimal.GetName()
<< " says "
<< rAnimal.Speak() << endl;
}
int main() {
Cat cCat("Fred");
Dog cDog("Garbo");
Report(cCat); // Fred says Meow
Report(cDog); // Garbo says Woof
}
115
Example
Cat acCats[] = { Cat("Fred"), Cat("Tyson"), Cat("Zeke") };
Dog acDogs[] = { Dog("Garbo"), Dog("Pooky"), Dog("Truffle") };
for (int i = 0; i < 3; i++)
cout << acCats[i].GetName() << " says " << acCats[i].Speak() << endl;
for (int I = 0; i < 3; i++)
cout << acDogs[i].GetName() << " says " << acDogs[i].Speak() << endl;
Cat cFred("Fred"), cTyson("Tyson"), cZeke("Zeke");
Dog cGarbo("Garbo"), cPooky("Pooky"), cTruffle("Truffle");
// Set up an array of pointers to animals, and set those pointers to our Cat and Dog objects
Animal *apcAnimals[] = { &cFred, &cGarbo, &cPooky, &cTruffle, &cTyson, &cZeke };
for (int i = 0; i < 6; i++)
cout << apcAnimals[i]->GetName() << " says " << apcAnimals[i]->Speak() << endl;
116
Virtual destructors
class Base {
public:
~Base() {
cout << "Calling ~Base()" << endl;
}
};
class Derived: public Base {
int* m_pnArray;
Derived *pDerived = new Derived(5);
Base *pBase = pDerived;
delete pBase;
// Calling ~Base()
public:
Derived(int nLength) {
m_pnArray = new int[nLength];
}
~Derived() // note: not virtual
{
cout << "Calling ~Derived()" << endl;
delete[] m_pnArray;
}
};
117
Virtual destructors
class Base {
public:
virtual ~Base() {
cout << "Calling ~Base()" << endl;
}
};
class Derived: public Base {
int* m_pnArray;
public:
Derived(int nLength) {
m_pnArray = new int[nLength];
}
virtual ~Derived() // note: virtual
{
cout << "Calling ~Derived()" << endl;
delete[] m_pnArray;
}
};
Derived *pDerived = new Derived(5);
Base *pBase = pDerived;
delete pBase;
// Calling ~Derived()
// Calling ~Base()
118
Overriding virtualization
// You probably won’t use this very often, but it’s good to know
class Base
{
public:
virtual const char* GetName() { return "Base"; }
};
class Derived: public Base
{
public
virtual const char* GetName() { return "Derived"; }
};
int main()
{
Derived cDerived;
Base &rBase = cDerived;
// Calls Base::GetName() instead of the virtualized Derived::GetName()
cout << rBase.Base
rBase.Base::
::GetName
GetName()
() << endl;
}
119
Early binding
• Binding refers to the process that is used to convert
identifiers (such as variable and function names) into
machine language addresses
addresses. Although binding is used
for both variables and functions, in this lesson we’re
going to focus on function binding
• Early binding (also called static binding
binding) means the
compiler is able to directly associate the identifier
name (such as a function or variable name) with a
machine address
address. Remember that all functions have a
unique machine address. So when the compiler
encounters a function call, it replaces the function call
with a machine language instruction that tells the CPU
to jump to the address of the function
120
Early binding
void PrintValue(int nValue)
{
cout << nValue;
}
int main()
{
PrintValue((5); // This is a direct function call
PrintValue
return 0;
}
121
Late Binding
• late binding or dynamic binding
– function pointers
int Add(int nX, int nY) {
return nX + nY;
}
int main() {
// Create a function pointer and make it point to the Add function
int (*
(*pFcn
pFcn)(
)(int
int,, int
int)) = Add;
cout << pFcn(5, 3) << endl; // add 5 + 3
return 0;
}
122
The virtual table
• To implement virtual functions, C++ uses a
special form of late binding known as the
virtual table
• The virtual table is a lookup table of functions
used to resolve function calls in a
dynamic/late binding manner
– other names: vtable
vtable,, virtual function table, virtual
method table, dispatch table
123
The virtual table
class Base
{
public:
virtual void function1() {};
virtual void function2() {};
};
class D1
D1: public Base
{
public:
virtual void function1() {};
};
class D2
D2: public Base
{
public:
virtual void function2() {};
};
124
The virtual table
– D1 cClass;
– Base *pClass = &cClass;
– pClass->function1();
•
Because cClass is a D1 object, cClass has it’s *__vptr set to the D1 virtual
table
•
Note that because pClass is a base pointer, it only points to the Base
portion of cClass. However, also note that *__vptr is in the Base portion of
the class, so pClass has access to this pointer. Finally, note that pClass>__vptr points to the D1 virtual table! Consequently, even though pClass is
of type Base, it still has access to D1′s virtual table
•
First, the program recognizes that function1() is a virtual function. Second,
uses pClass->__vptr to get to D1′s virtual table. Third, it looks up which
version of function1() to call in D1′s virtual table. This has been set to
D1::function1().
Therefore,
pClass->function1()
resolves
to
D1::function1()!
125
Pure virtual (abstract) functions
• C++ allows you to create a special kind of
virtual function called a pure virtual
function (or abstract function) that has no
body at all! A pure virtual function simply acts
as a placeholder that is meant to be
redefined by derived classes
• When we add a pure virtual function to our
class, we are effectively saying, “it
it is up to the
derived classes to implement this function
function”
126
Abstract classes
• Using a pure virtual function has two main
consequences:
– First, any class with one or more pure virtual
functions becomes an abstract base class
class, which
means that it can not be instantiated!
– Second, any derived class must define a body for
this function, or that derived class will be
considered an abstract base class as well
127
Abstract class
class Base {
public:
// a normal nonnon-virtual function
const char* SayHi() { return "Hi"; }
// a normal virtual function
virtual const char* GetName() { return "Base"; }
// a pure virtual function
virtual int GetValue()
= 0;
};
int main()
{
Base cBase; // Error
cBase.GetValue(); // what would this do?
}
128
Abstract class
class Animal {
protected:
string m_strName;
Animal(string strName) : m_strName(strName) {}
public:
string GetName() { return m_strName; }
virtual const char* Speak() { return "???"; }
};
class Animal {
class Cow: public Animal {
…
public:
virtual const char* Speak() = 0;
Cow(std::string strName):
};
Animal(strName) { }
// We forgot to redefine Speak
};
129
Interface classes
• An interface class is a class that has no members
variables, and where all of the functions are
pure virtual! In other words, the class is purely a
definition, and has no actual implementation
– virtual destructor or pure virtual functions
• Interfaces are useful when you want to define
the functionality that derived classes must
implement, but leave the details of how the
derived class implements that functionality
entirely up to the derived class
– class Dclass : public Bclass
Bclass,, public Interface
Interface1
1, public
Interface2
Interface
2, …
130
Interface classes
class IErrorLog {
virtual bool OpenLog(const char *strFilename) = 0;
virtual bool CloseLog() = 0;
virtual bool WriteError(const char *strErrorMessage) = 0;
};
class FileErrorLog : IErrorLog {
// implementation of IErrorLog
}
class ScreenErrorLog: IErrorLog {
// implementation of IErrorLog
}
double MySqrt(double dValue, ScreenErrorLog &cLog) { /*…*/ }
double MySqrt(double dValue, FileErrorLog &cLog) { /*…*/ }
double MySqrt(double dValue, IErrorLog &cLog) { /*…*/
/*…*/}
See Example of slide #115
131
Composition Instead Of Inheritance
• Is
Is--A vs Has
Has--a
class Mouth {
public:
void eat(Food bite);
};
// A person is a mouth
class Person: public Mouth {
};
// A person has a mouth
class Person {
public:
void eat(Food bite) {
itsMouth.eat(bite);
}
private:
Mouth itsMouth
itsMouth;;
};
132
Inheritance and static member functions
• static member act the same as non-static member:
– They inherit into the derived class
– If you redefine a static member, all the other overloaded
functions in the base class are hidden
– If you change the signature of a function in the base class,
all the base class versions with that function name are
hidden (this is really a variation of the previous point)
• However, static member functions cannot be virtual
133
Function templates - overview
int getMax(int nX, int nY)
{
return (nX > nY) ? nX : nY;
}
double getMax(double dX, double dY)
{
return (dX > dY) ? dX : dY;
}
// …
134
Function templates - overview
// this is the template parameter declaration
template <typename
typename Type
Type> // template <class
class Type>
Type
Type getMax(Type
Type dX, Type dY)
{
return (dX > dY) ? dX : dY;
}
// …
int nValue = getMax
getMax((3, 7);
// returns 7
double dValue = getMax
getMax((6.34
34,, 18
18..523
523);
);
// returns 18
18..523
char chValue = getMax
getMax('a',
('a', '6
'6');
// returns 'a'
int n2 = getMax
getMax<<int>(
>(3
3, 7);
// returns 7
double d2 = getMax
getMax<<int
int>(
>(6
6.34
34,, 18
18..523
523);
); // returns 18
135
Function templates - overview
// this is the template parameter declaration
// template <class T, class U>
template <typename
typename T, typename U>
T max(T dX, U dY)
{
return (dX > dY) ? dX : dY;
}
// …
int nValue = max(
max(3
3, 7);
// returns 7
double dValue = max(
max(6
6.34
34,, 18
18..523
523);
); // returns 18
18..523
char chValue = max('a', '6
'6');
// returns 'a'
136
Function templates - overview
template <typename
typename Type
Type>
Type getMax(Type
Type dX, Type dY)
{
return (dX > dY) ? dX : dY;
}
class Cents
{
private:
int m_nCents;
public:
Cents(int nCents) :
m_nCents(nCents) { }
};
Cents cNickle(5);
Cents cDime(10);
Cents cBigger =
getMax(cNickle, cDime);
getMax
/* C++ will create a template
instance for max() that looks
like this: */
Cents max(Cents tX, Cents tY)
{
return (tX > tY) ? tX : tY;
}
137
Function templates - Exercise
template <class
class T>
Write Cents Class?
T Average(T *atArray, int nNumValues)
{
T tSum = 0;
for (int nCount=0; nCount < nNumValues; nCount++)
tSum += atArray[nCount];
tSum /= nNumValues;
return tSum;
}
Cents cArray[] = { Cents(5), Cents(10), Cents(15), Cents(14) };
cout << Average(
Average(cArray
cArray,, 4) << endl;
138
Templates classes
template <typename
typename T>
// template <class T>
class Array {
int m_nLength;
T *m_ptData;
public:
Array() { m_nLength = 0; m_ptData = 0; }
Array(int nLength) {
m_ptData= new T[nLength];
m_nLength = nLength;
}
~Array(){ delete[] m_ptData; }
T& operator[](int nIndex) {
assert(nIndex >= 0 && nIndex < m_nLength);
return m_ptData[nIndex];
}
int GetLength();
};
template <typename
typename T>
int Array<T>::
Array<T>::GetLength
GetLength()
() { return m_nLength; }
int main() {
Array<int>> anArray(12);
Array<int
Array<double> adArray(12);
for (int n = 0; n < 12; n++)
{
anArray[n] = n;
adArray[n] = n+ 0.5;
}
return 0;
}
139
Expression parameters
• A template expression parameter is a
parameter that does not substitute for a
type, but is instead replaced by a value
type
• An expression parameter can be any of the
following:
– A value that has an integral type or enumeration
– A pointer or reference to an object
– A pointer or reference to a function
– A pointer or reference to a class member
function
140
Expression parameters
// nSize is the expression parameter
template <typename
typename T, int nSize
nSize>
class Buffer {
// The expression parameter controls the size of the array
T m_atBuffer[nSize];
public:
T* GetBuffer() { return m_atBuffer; }
T& operator[](int nIndex) {
return m_atBuffer[nIndex];
}
};
// declare an integer buffer with room for 12 chars
Buffer<int
Buffer<
int,, 12
12>> cIntBuffer;
// declare a char buffer with room for 31 chars
Buffer<char, 31
31>> cCharBuffer;
141
Expression parameters
template <class
class T, int N>
class mysequence {
T memblock [N];
public:
void setmember (int x, T value);
};
template <class
class T, int N>
void mysequence
mysequence<T,N>
<T,N>::
::setmember (int x, T value) {
memblock[x]=value;
}
142
Expression parameters
/* An expression parameter can be any of the following
following:: A value
that has an integral type … */
template <typename T, float nSize>
class C{
};
template <typename T, double nSize>
class C{
};
143
Template specialization
template <typename T>
class Storage {
T m_tValue;
public:
Storage(T tValue) {
m_tValue = tValue;
}
~Storage() {
}
void Print()
{
cout << m_tValue;
}
};
Storage<int>> nValue(5);
Storage<int
Storage<double> dValue(6.7);
nValue.Print(); // ok
dValue.Print(); // ok
// but !!!
char *strString = new char[40];
cin >> strString;
Storage<char*> strValue(strString);
delete strString
strString;;
// Print out our value
// This will print garbage
strValue.Print();
// fix it using template specialization
144
Template specialization - member functions
template <typename T>
class Storage {
T m_tValue;
public:
Storage(T tValue) { m_tValue = tValue; }
~Storage() {}
void Print(){ cout << m_tValue;}
};
Storage<char*
char*>::Storage(char* tValue) {
m_tValue = new char[strlen(tValue)+1];
strcpy(m_tValue, tValue);
}
specialization
Storage<char*
char*>::~Storage() {
delete[] m_tValue;
}
for char *
145
Class template specialization
template <typename T>
class Storage8 {
T m_tType[8];
// Define a Storage8
Storage8 for integers
Storage8<int> cIntStorage;
// Define a Storage8
Storage8 for bool
Storage8<bool> cBoolStorage;
public:
void Set(int nIndex, const T & tType)
{
m_tType[nIndex] = tType;
}
it’s possible to compress
const T& Get(int nIndex)
all 8 bools into a single
{
byte, eliminating the
return m_tType[nIndex];
wasted space altogether
}
};
146
Class template specialization
template <typename T>
class Storage8 {
T m_tType[8]; /* … */
};
// the following is a template class with no templated parameters
template <>
class Storage
Storage8
8<bool
bool>> // we're specializing Storage8
Storage8 for bool
{
unsigned char m_tType;
public:
void Set(int nIndex, bool tType) {
unsigned char nMask = 1 << nIndex;
if (tType) m_tType |= nMask; else m_tType &= ~nMask;
}
bool Get(int nIndex)
{
unsigned char nMask = 1 << nIndex;
return m_tType & nMask;
}
};
147
Partial template specialization
// nSize is the expression parameter
template <typename
typename T, int nSize
nSize>
class Buffer {
// The expression parameter controls the size of the array
T m_atBuffer[nSize];
Buffer<char, 10
10>> cChar10Buffer;
public:
strcpy(cChar10Buffer.GetBuffer(), "Ten");
T* GetBuffer() { return m_atBuffer; }
PrintBufferString(cChar10Buffer); // Ten
Buffer<int
Buffer<
int,, 10
10>> cInt10Buffer;
T& operator[](int nIndex) {
for (int n=0; n < 10; n++)
return m_atBuffer[nIndex];
cInt10Buffer[n] = n;
}
// print the address of pointer
};
PrintBufferString(cInt
PrintBufferString
(cInt10
10Buffer);
Buffer);
template <typename
typename T, int nSize
nSize>
void PrintBufferString(Buffer<T,
Buffer<T, nSize
nSize>> &rcBuf)
&
Problem : ensure that
only arrays of type char
{
can be passed to function
std::cout << rcBuf.GetBuffer() << std::endl;
}
148
Partial template specialization
// Solution 1
void PrintBufferString(Buffer<char,
Buffer<char, 10
10>> &rcBuf)
&
{
std::cout << rcBuf.GetBuffer() << std::endl;
}
// Problem of solution 1
int main()
{
Buffer<char, 10
10>> cChar10Buffer;
Buffer<char, 11
11>> cChar11Buffer;
strcpy(cChar10Buffer.GetBuffer(), "Ten");
strcpy(cChar11Buffer.GetBuffer(), "Eleven");
PrintBufferString(cChar10Buffer);
PrintBufferString(cChar
PrintBufferString
(cChar11
11Buffer)
Buffer); // this will not compile
return 0;
}
149
Partial template specialization
// Solve problem of solution 1 using Partial template specialization
template<int
template<
int nSize
nSize>>
void PrintBufferString(Buffer<char,
Buffer<char, nSize
nSize>> &rcBuf)
&
{
std::cout << rcBuf.GetBuffer() << std::endl;
}
int main()
{
Buffer<char, 10
10>> cChar10Buffer;
Buffer<char, 11
11>> cChar11Buffer;
strcpy(cChar10Buffer.GetBuffer(), "Ten");
strcpy(cChar11Buffer.GetBuffer(), "Eleven");
PrintBufferString(cChar10Buffer); // Ok
PrintBufferString(cChar
PrintBufferString
(cChar11
11Buffer)
Buffer); // Ok
return 0;
}
150
Exception handling
• provides a mechanism to decouple handling of errors or
other exceptional circumstances from the typical control
flow of your code
• Exceptions in C++ are implemented using three keywords
that work in conjunction with each other: throw
throw, try
try,
and catch
• throw statement is used to signal that an exception or
error case has occurred
–
–
–
–
–
throw -1; // throw a literal integer value
throw ENUM_INVALID_INDEX; // throw an enum value
throw “Error 10
10";
"; // throw a char* or string
throw dX
dX;; // throw a double variable that was previously defined
throw MyError
MyError("Fatal
("Fatal Error"); // Throw an object of class MyError
151
Exception handling
try
{
// Statements that may throw exceptions you want to handle
throw -1;
}
// Any exceptions of type int thrown within the above try block
catch (int
(int))
{
cerr << "We caught an exception of type int" << endl;
}
// Any exceptions of type double thrown within the above try block
catch (double)
{
cerr << "We caught an exception of type double" << endl;
}
152
Exception handling - Example
double MySqrt(double dX) {
if (dX < 0.0)
throw "Can not take sqrt of negative number";
number"
return sqrt(dX);
}
int main() {
double dX = -60;
try {
cout << "The sqrt of " << dX << " is " << MySqrt
MySqrt((dX
dX)) << endl;
}
catch (char* strException) // catch exceptions of type char*
{
cerr << "Error: " << strException << endl;
}
}
153
Exception handling - stack unwinding
void Last() {
cout << "Start Last" << endl;
cout << "4 throwing int";
throw -1;
cout << "End Last" << endl;
}
void Third(){
cout << "Start Third" << endl;
Last();
cout << "End Third" << endl;
}
void Second() {
cout << "Start Second" << endl;
try {
Third();
}
catch(double) {
cerr << “2 caught double“;
}
cout << "End Second" << endl;
}
void First() {
cout << "Start First" << endl;
try {
Second();
} catch (int
(int)) {
cerr << "1 caught int\n";
} catch (double) {
cerr << "1 caught double\n";
}
cout << "End First" << endl;
}
int main() {
cout << "Start main" << endl;
try {
First();
}
catch (int
(int)) {
cerr << "main caught int\n";
}
cout << "End main" << endl;
}
Start main
Start First
Start Second
Start Third
Start Last
4 throwing int
1 caught int
End First
End main
154
Catch-all handlers
• C++ provides us with a mechanism to catch all
types of exceptions
– it should be placed last in the catch block chain
try
{
throw 5; // throw an int exception
}
catch (double dX
dX){
){
cout << "caught an exception of type double: " << dX << endl;
}
catch (...) {//
{// catchcatch-all handler
cout << “caught an exception of an undetermined type" << endl;
}
155
Exception handling - Example
double MySqrt(double dX) {
if (dX < 0.0) throw "Can not take sqrt of negative number";
number"
return sqrt(dX);
}
int main() {
double dX = -60;
cout << "The sqrt of " << dX << " is " << MySqrt
MySqrt((dX
dX)) << endl;
}
/*main terminates with an unhandled exception, the OS will generally
notify you that an unhandled exception error has occurred */
int main(){
try {
// program
}
catch(...) {
cerr << "Abnormal termination" << endl;
}
return 1;
}
156
Exception specifiers
• a mechanism that allows us to use a function declaration
to specify whether a function may or will not throw
exceptions. This can be useful in determining whether a
exceptions
function call needs to be put inside a try block or not
– an empty throw statement to denote that a function does not
throw any exceptions outside of itself
• int DoSomething() throw()
throw(); // does not throw exceptions
– a specific throw statement to denote that a function may throw
a particular type of exception
• int DoSomething() throw(double)
throw(double); // may throw a double
– a catch-all throw statement to denote that a function may throw
an unspecified type of exception
• int DoSomething() throw(
throw(...
...)); // may throw anything
• exception specifies are rarely used in practice, are not well
supported by compilers
157
std::exception
• The C++ standard library comes with an
exception class that is used by many of the
other standard library classes.
class exception {
public:
/* … */
virtual const char* what() const throw();
}
158
Exception classes
#include <exception>
class myException
myException:: public exception {
virtual const char* what() const throw()
{
return "My exception happened";
}
};
int main () {
try {
/* ... */
throw myException
myException();
();
}
catch (exception&
exception& e) {
cout << e.what() << endl;
}
return 0;
}
159
Exception dangers and downsides
• What happens if WriteFile() fails
try
{
OpenFile(strFilename);
WriteFile((strFilename
WriteFile
strFilename,, strData
strData);
);
CloseFile(strFilename);
}
catch (FileException
FileException &cException
cException)
{
cerr << "Failed to write to file: " << cException.what() << endl;
}
160
Exception dangers and downsides
try
{
OpenFile(strFilename);
WriteFile((strFilename
WriteFile
strFilename,, strData
strData);
);
CloseFile(strFilename);
}
catch (FileException
FileException &cException
cException)
{
CloseFile((strFilename
CloseFile
strFilename);
);
cerr << "Failed to write to file: " << cException.what() << endl;
}
161
Exception handling - example
try
{
int * myarray= new int[1000];
}
catch (bad_alloc
bad_alloc&
&)
{
cout << "Error allocating memory." << endl;
}
162