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

Advanced C++ PDF

This document provides an overview of topics that will be covered in an Advanced C++ course, including object-oriented programming (OOP) concepts in C++, templates, iterators, algorithms, exception handling, memory management, inheritance techniques, and multithreading. The document consists of lecture notes that begin with a discussion of why C++ was created and an introduction to classes and objects in C++. It covers OOP principles like abstraction, encapsulation, and inheritance with examples. Association, aggregation and composition relationships are demonstrated.
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
755 views

Advanced C++ PDF

This document provides an overview of topics that will be covered in an Advanced C++ course, including object-oriented programming (OOP) concepts in C++, templates, iterators, algorithms, exception handling, memory management, inheritance techniques, and multithreading. The document consists of lecture notes that begin with a discussion of why C++ was created and an introduction to classes and objects in C++. It covers OOP principles like abstraction, encapsulation, and inheritance with examples. Association, aggregation and composition relationships are demonstrated.
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 257

Advanced C++

Cosmin Stan
[email protected]

Topics

OOP in C++
Copying and Conversions
Scope
Delegation
Subscripting
Template functions
Template classes
Template techniques
Iterators and Algorithms
Exception handling
Memory management
Reference counting
Inheritance techniques
Functional abstraction
C++11 language enhancements
Multithreading techniques
2

Day 1

OOP In C++

Why was C++ created?

I wanted to write efficient systems programs in the


styles encouraged by Simula67. To do that, I added
facilities for better type checking, data abstraction,
and object-oriented programming to C. The more
general aim was to design a language in which I
could write programs that were both efficient and
elegant. Many languages force you to choose
between those two alternatives. The specific tasks
that caused me to start designing and implementing
C++ (initially called "C with Classes") had to do with
distributing operating system facilities across a
network. - Bjarne Stroustrup, Bjarne Stroustrup's FAQ
5

Classes and Objects

Data Structures are a form of storing and organising


data

Classes are an expanded concept of Data


Structures

Unlike Data Structures, classes can also contain


functions as members

Objects are instantiations of classes


6

Example
//

01_01_ClassesAndObjects

#include <iostream>
class Point{ // Point is a class
public:
int x, y;
void print(){
std::cout << "(" << x << "," << y << ")\n";
}
void multiply(int factor){
x *= factor;
y *= factor;
}
};
int main() {
Point p; // p is an object of class Point
p.print();
p.x = 2;
p.y = 3;
p.print();
p.multiply(3);
p.print();
return 0;
}

Output:
(1606416240,32767)
(2,3)
(6,9)

Example (using a constructor)


//

01_01a_ClassesAndObjects

#include <iostream>
class Point{ // Point is a class
public:
int x, y;
//

Point():x(0),y(0){}
Point(){
x = 0;
y = 0;
}
void print(){
std::cout << "(" << x << "," << y << ")\n";
}

};

void multiply(int factor){


x *= factor;
y *= factor;
}

int main() {
Point p; // p is an object of class Point
p.print();
p.x = 2;
p.y = 3;
p.print();
p.multiply(3);
p.print();
return 0;
}

Output:
(0,0)
(2,3)
(6,9)

Example (using two constructors)


//

01_01b_ClassesAndObjects

#include <iostream>
class Point{ // Point is a class
public:
int x, y;
Point():x(0),y(0){}
Point(int x, int y):x(x), y(y){}
void print(){
std::cout << "(" << x << "," << y << ")\n";
}

};

void multiply(int factor){


x *= factor;
y *= factor;
}

int main() {
Point p; // p is a object of class Point
p.print();
Point q(1,2); // q is an object of class Point
q.print();
q.multiply(10);
q.print();
return 0;
}

Output:
(0,0)
(1,2)
(10,20)

Abstraction

Abstraction is a technique that relies on the


separation of interface and implementation

In C++ classes can provide a great level of


abstraction

Abstraction separates code into interface and


implementation

In C++ we can implement abstraction using access


modifiers
10

Access modifiers

public members are accessible from anywhere


outside the class

private members cannot be accessed from outside


the class. They are available only inside the class or
from friend functions

protected members cannot be access from outside


the class, but, unlike private members they are
accessible to child classes (or derived classes)
11

Example
//

01_02_Abstraction

Point q(1,2);
q.multiply(10);
q.print();

#include <iostream>

ShowPointContents(&q);
return 0;

class Point{ // Point is a class


friend void ShowPointContents(Point * p);
private:
int x, y;
public:
Point():x(0),y(0){}
Point(int x, int y):x(x), y(y){}

void print(){
std::cout << "(" << x << "," << y << ")\n";
}
void multiply(int factor){
x *= factor;
y *= factor;
}
};
void ShowPointContents(Point * p){
std::cout << "(" << p->x << "," << p->y << ")\n";
}
int main() {
Point p;
p.print();
// p.x = 2; // this would not compile

Output:
(0,0)
(10,20)
(10,20)

12

Encapsulation

Encapsulation is the inclusion within an object of all


the resources (function and data) needed for the
object to function

As a good practice, we usually publish the interface


and hide the internal data

Together with abstraction, encapsulation is used to


implement data hiding

13

Example
//

01_03_Encapsulation

void printEquation(){
std::cout << "Equation: " << a << "*x^2" << ((b > 0)?"+" :
"" )<< b << "x" << ((c > 0)?"+" : "")<< c << "\n";
}
private:
QuadraticEquation (): a(0), b(0), c(0) { };
int a, b, c, delta;
float x1, x2;
bool isComplex;
};

#include <iostream>
#include <math.h>
class QuadraticEquation{
public:
QuadraticEquation(int a, int b, int c): a(a), b(b), c(c){
delta = b*b - 4*a*c;
isComplex = false;
if (delta > 0){
x1 = ((-b) - sqrt(delta)) / (2*a);
x2 = ((-b) + sqrt(delta)) / (2*a);
}
else if (delta == 0){
x1 = x2 = (-b)/(2*a);
}
else {
isComplex = true;
}
}

void PrintSolutions(QuadraticEquation &eq){


eq.printEquation();
int x1, x2;
if (eq.getSolutions(x1, x2)){
std::cout << "x1 = " << x1 << "\nx2 = " << x2 << "\n";
}
else {
std::cout << "The solutions to this equation are complex
numbers\n";
}
}

bool getSolutions(int &x1, int &x2){


if (isComplex){
return false;
}
x1 = this->x1;
x2 = this->x2;
}

int main(int argc, const char * argv[]) {


QuadraticEquation eq1(1, -4, 1);
QuadraticEquation eq2(1, -2, 1);
QuadraticEquation eq3(1, 1, 1);
PrintSolutions(eq1);
PrintSolutions(eq2);
PrintSolutions(eq3);
return 0;
}

return true;

Equation: 1*x^2-4x+1
x1 = 0
x2 = 3
Equation: 1*x^2-2x+1
x1 = 1
x2 = 1
Equation: 1*x^2+1x+1
The solutions to this equation are complex numbers

14

Association

Association is a relationship between two or more


objects, where each has its own lifetime

Each object can exist without any other

Associated objects do not have any subordination


relationship

15

Aggregation and Composition

Aggregation creates more complex objects from


simple, independent objects. There is a
subordination relationship between objects (one is
the parent of the other)

Composition is a technique used in OOP to create


more complex objects using simpler ones. In
composition the more complex object cannot exist
without the simpler objects

16

Association, Aggregation, Composition - Example


//

01_04_Association_Aggregation_Composition

std::cout << "Apps in " << name << ":\n";


for (int i = 0; i < appCount; ++i){
std::cout << applications[i]->getName() <<

#include <iostream>
"\n";
class Application{
public:
Application(const char *name) : name(name){};
std::string & getName(){
return name;
}
private:
Application() {};
std::string name;
};
class AppStore{
public:
AppStore(const char *name): name(name), appCount(0) {
applications = new Application*[10];
};

}
}
private:
Application ** applications;
int appCount;
std::string name;

int main(int argc, const char * argv[]) {

AppStore() {};
};
class Smartphone{
public:
Smartphone(const char *name): name(name), appCount(0)
{
applications = new Application*[10];
};
~Smartphone(){
delete [] applications;
}
void installApplication(AppStore &appStore,
std::string const &appName){
Application *newApp =
appStore.getApplication(appName);

Application *getApplication (std::string const


&appName){
Application *theApp = nullptr;

if (newApp != nullptr){
applications[appCount++] = newApp;
}

for (int i = 0; i < appCount; ++i){


if (applications[i]->getName() == appName){
theApp = applications[i];
}
}
return theApp;

Smartphone() {};
};

~AppStore(){
delete [] applications;
}
void addApplication (Application* app){
applications[appCount++] = app;
}

private:
Application ** applications;
int appCount;
std::string name;

Application compass("Compass"), maps("MAPS"),


facebook("Facebook");
AppStore googlePlay("Google Play"), appStore ("App
Store");
Smartphone samsung("SAMSUNG"), iphone("iPhone");
googlePlay.addApplication(&compass);
googlePlay.addApplication(&maps);
appStore.addApplication(&compass);
appStore.addApplication(&maps);
appStore.addApplication(&facebook);
samsung.installApplication(googlePlay,
std::string("Compass"));
samsung.installApplication(googlePlay,
std::string("Facebook"));
iphone.installApplication(appStore,
std::string("Facebook"));
iphone.installApplication(appStore,
std::string("MAPS"));

googlePlay.listApps();
appStore.listApps();

void listApps(){
std::cout << "Apps installed on " << name << ":\n";
for (int i = 0; i < appCount; ++i){
std::cout << applications[i]->getName() <<

samsung.listApps();
iphone.listApps();

"\n";

return 0;

}
void listApps(){

Apps in Google Play:


Compass
MAPS
Apps in App Store:
Compass
MAPS
Facebook
Apps installed on SAMSUNG:
Compass

Apps installed on iPhone:


Facebook
MAPS

17

Inheritance

One of the most important concepts in OOP

In OOP, a new class can inherit the members of an


existing class

We call the existing class is the base class and the


new class the derived class

18

Inheritance - access control


ACCESS

public

protected

private

Same class

YES

YES

YES

Derived classes

YES

YES

NO

Outside
classes

YES

NO

NO

19

Types of inheritance

public inheritance
public members of the base class become public members of the derived
class
protects members of the base class become protected members of the derived
class

protected inheritance
public and protected members of the base class become protected members
of the derived class

private inheritance
public and protected members of the base class become private members of
the derived class

NOTE!
The private members of a class are never accessible to the derived classes. They
are, however, accessible to friend classes or functions.

20

Inheritance Example
//

01_05_Inheritance

};

#include <iostream>

int main(int argc, const char * argv[]) {


Phone simplePhone("Nokia");
Smartphone smartphone("Samsung", "Android");

class Phone{
public:
Phone(const char* manufacturer) :
manufacturer(manufacturer){};
std::string const & getManufacturer (){
return manufacturer;
}
protected:
std::string manufacturer;
private:
Phone() : manufacturer("") {};
};

std::cout << "Simple phone manufacturer:" <<


simplePhone.getManufacturer() << "\n";
std::cout << "Smartphone manufacturer: " <<
smartphone.getManufacturer() << "\n";
std::cout << "Smartphone os: " << smartphone.getOS() <<
"\n";
smartphone.printManufacturerAndOs();
return 0;
}

class Smartphone : public Phone {


public:
Smartphone (const char *manufacturer, const char *os) :
Phone(manufacturer), os(os){};
std::string const & getOS(){
return os;
}
void printManufacturerAndOs(){
std::cout << "Manufacturer: " << manufacturer << "; OS: "
<< os << "\n";
}
private:
std::string os;

Simple phone manufacturer:Nokia


Smartphone manufacturer: Samsung
Smartphone os: Android
Manufacturer: Samsung; OS: Android

21

Multiple Inheritance (MI)

Multiple inheritance allow new classes to inherit


from multiple classes

For example, a Button is a Shape, but it also is


Drawable

22

MI - The Diamond Problem


Button

Drawable

Rectangle

Object
23

Multiple Inheritance Example


//

01_06_Multiple_Inheritance

Smartphone (const char *manufacturer, const char *os) :


Phone(manufacturer), Computer(os) {};
void printDetails(){
std::cout << "I am made by " << getManufacturer() << "
and I run " << getOS() << "\n";
}
};

#include <iostream>
class Phone{
public:
Phone(const char *manufacturer) :
manufacturer(manufacturer){};
std::string const & getManufacturer (){
return manufacturer;
}
private:
std::string manufacturer;
Phone() : manufacturer("") {};
};

int main(int argc, const char * argv[]) {


Smartphone galaxyPhone("Samsung", "Android");
Smartphone iPhone("Apple", "iOS");
galaxyPhone.printDetails();
iPhone.printDetails();
return 0;
}

class Computer {
public:
Computer (const char *os) : os(os){};
std::string const & getOS(){
return os;
}
private:
std::string os;
Computer() : os("") {};
};
class Smartphone: public Phone, public Computer {
public:

I am made by Samsung and I run Android


I am made by Apple and I run iOS

24

Polymorphism

It is the core of object oriented programming

Polymorphism is the ability of objects or methods to behave


differently in different context

In C++, pointers to base and derived classes are type compatible

A virtual method is a method that can be redefined in a derived


class

Unlike a virtual method, a method redefined in a derived class


cannot be accessed through a pointer of an object of the base
class
25

Polymorphism

We call a polymorphic class a class that declares or


inherits a virtual method

A pure virtual method is a method that doesnt have


an implementation in the base class

Classes that have pure virtual methods cannot be


instantiated

In C++, polymorphism only works with non-value


types: references and pointers!
26

Example
//

01_07_Polymorphism

#include <iostream>

};

class Computer {
public:
virtual std::string manufacturer(){
return std::string("Dell");
}
virtual std::string whatAmI(){
return std::string("I'm a computer");
}

class Tablet: public Computer {


public:
virtual std::string whatAmI(){
return std::string("I'm a tablet");
}
};
int main(int argc, const char * argv[]) {
Computer *computer = new Computer();
Computer *smartphone = new Smartphone();
Computer *iPhone = new IPhone();
Computer *tablet = new Tablet();

virtual int getStorage(){


return storage;
}
};

virtual std::string whatAmI(){


return std::string("I'm an iPhone");
}

int storage = 10;

std::cout << computer->whatAmI() << " made by " << computer>manufacturer() << "\n";
std::cout << smartphone->whatAmI() << " made by " <<
smartphone->manufacturer() << "\n";
std::cout << iPhone->whatAmI() << " made by " << iPhone>manufacturer() << "\n";
std::cout << tablet->whatAmI() << " made by " << tablet>manufacturer() << "\n";

class Smartphone: public Computer{


public:
virtual std::string manufacturer(){
return std::string("Apple");
}
virtual std::string whatAmI(){
return std::string("I'm a smartphone");
}
virtual std::string getNetwork(){
return std::string("Orange");
}
virtual ~Smartphone(){};
};

Smartphone *iPhone5 = new IPhone();


std::cout << "My network is " << iPhone5->getNetwork() << "\n";
delete computer; delete smartphone; delete iPhone; delete
tablet; delete iPhone5;
return 0;

class IPhone: public Smartphone {


public:

Output:
I'm a computer made by Dell
I'm a smartphone made by Apple
I'm an iPhone made by Apple
I'm a tablet made by Dell
My network is Orange

27

How the computer sees it


cstan-mac:01_07_Polymorphism cosmin$ clang -cc1 -fdump-record-layouts main.cpp
*** Dumping AST Record Layout
0 | class Computer
0 |
(Computer vtable pointer)
8 |
int storage
| [sizeof=16, dsize=12, align=8
| nvsize=12, nvalign=8]
*** Dumping AST Record Layout
0 | class Smartphone
0 |
class Computer (primary base)
0 |
(Computer vtable pointer)
8 |
int storage
| [sizeof=16, dsize=12, align=8
| nvsize=12, nvalign=8]
*** Dumping AST Record Layout
0 | class IPhone
0 |
class Smartphone (primary base)
0 |
class Computer (primary base)
0 |
(Computer vtable pointer)
8 |
int storage
| [sizeof=16, dsize=12, align=8
| nvsize=12, nvalign=8]
*** Dumping AST Record Layout
0 | class Tablet
0 |
class Computer (primary base)
0 |
(Computer vtable pointer)
8 |
int storage
| [sizeof=16, dsize=12, align=8
| nvsize=12, nvalign=8]

28

Copying and conversions

Type casting

C++ is a strong-typed language

Conversions that imply different interpretations of the value,


require explicit conversion. In C++ this is know as typecasting

There are 2 syntaxes for this: functional and c-like

The functionality of these generic forms of type-casting is


enough for most needs with fundamental data types.
However, these operators can be applied indiscriminately on
classes and pointers to classes, which can lead to code that,
while being syntactically correct, can cause runtime errors
30

Example
//

02_01_Type_Casting

#include <iostream>
class Point{ // Point is a class
public:
Point(int x, int y):x(x), y(y){}
void print(){
std::cout << "(" << x << "," << y << ")\n";
}
private:
int x, y;
Point():x(0),y(0){}
};
int main(int argc, const char * argv[]) {
int nr(10);
Point * p = (Point*)&nr;
p->print();
return 0;
}

Output:
(10,1606416256)

31

static_cast <new_type>(expression)

converts pointers to related classes

can perform both upcasts and downcasts

no checks are performed, it is up to us to ensure


that the cast is safe

converts from void* to any pointer type. It


guarantees that, if the void* value was obtained by
converting from the same pointer type, the resulting
pointer value will be the same.
32

static_cast <new_type>(expression)

converts integers, floats and enums to enum types

can explicitly call a single-argument constructor or a


conversion operator

converts to rvalue references

converts enum classes to integers or floats

converts any type to void


33

Example
//

02_02_Static_Cast

void ShowDetails(void *pointerToAHumanObject){


Human *human = static_cast<Human *>(pointerToAHumanObject);
std::cout << "Address value of human after re-cast is:" <<
human << "\n";
human->toString();
}

#include <iostream>
class Animal
{
public:
void toString(){
std::cout << "I'm a generic Animal!\n";
}
};

int main(int argc, const char * argv[]) {


Duck *duck = new Duck();
Human *human = new Human();

class Duck : public Animal


{
public:
Duck() {
name = new std::string("Donald");
}
void toString(){
std::cout << "I'm a duck and my name is " << *name << "!
\n";
}
virtual ~Duck(){
delete name;
}
private:
std::string *name;
};

duck->toString();
human->toString();
Animal *animal = static_cast<Animal *>(duck);
animal->toString();
Animal *anotherAnimal = new Animal();
Duck *anotherDuck = static_cast<Duck *>(anotherAnimal);
//anotherDuck->toString(); // CRASH!!
std::cout << "Address value of human is:" << human << "\n";
void *pToHuman = static_cast<void *>(human);
ShowDetails(pToHuman);

class Human
{
public:
virtual void toString(){
std::cout << "I'm a human and my name is Joe!\n";
}
};

Output:
I'm a duck and my name is Donald!
I'm a human and my name is Joe!
I'm a generic Animal!
Address value of human is:0x100100b50
Address value of human after re-cast is:0x100100b50
I'm a human and my name is Joe!

34

delete duck;
delete human;
return 0;

dynamic_cast <new_type>(expression)

can only be used with pointers and references to


classes (except void*)

ensures that the result is a valid complete object of


the required type

can both upcast and downcast (as long as the


pointed object is a valid complete object of the
target type)

35

dynamic_cast <new_type>(expression)

If the pointer cast fails, it returns a null pointer of the new type.

If the reference cast fails, it throws a std::bad_cast exception

Can be used to add const-ness, but it cannot remove the const-ness or volatility
of an object

If performed on null, the result will be a null object of the new type

If performed on a polymorphic type to cast to a pointer to void, the result will be


a pointer to the most derived object pointed or referenced by expression

Used in a constructor or a destructor (directly or indirectly), and expression


refers to the object that's currently under construction/destruction, the object is
considered to be the most derived object. If new_type is not a pointer or
reference to the construction's/destructor's own class or one of its bases, the
behaviour is undefined.
36

dynamic_cast <new_type>(expression)

A runtime check is performed if expression is a polymorphic type (Base)


and new_type is a pointer or reference to a derived type (Derived):

The most derived object pointed/identified by expression is


examined. If, in that object, expression points/refers to a public
base of Derived, and if only one sub-object of Derived type is
derived from the sub-object pointed/identified by expression, then
the result of the cast points/refers to that Derived sub-object. (This is
known as a downcast".)

Otherwise, if expression points/refers to a public base of the most


derived object, and, simultaneously, the most derived object has an
unambiguous public base class of type Derived, the result of the
cast points/refers to that Derived (This is known as a sidecast".)

Otherwise, the runtime check fails. If the dynamic_cast is used on


pointers, the null pointer value of type new_type is returned. If it was
used on references, the exception std::bad_cast is thrown.
37

Example
//

02_03_Dynamic_Cast
int main(int argc, const char * argv[]) {
Animal *animal = new Animal();
Animal *duck = new Duck();

#include <iostream>
class Animal
{
public:
virtual void toString(){
std::cout << "I'm a generic Animal!\n";
}
};

animal->toString();
duck->toString();
Duck *duckFromDuck = dynamic_cast<Duck *>(duck);
if (duckFromDuck == nullptr) std::cout << "Invalid cast
Duck from Duck!\n";
else duckFromDuck->toString();

class Duck : public Animal


{
public:
Duck() {
name = new std::string("Donald");
}
virtual void toString(){
std::cout << "I'm a duck and my name is " << *name <<
"!\n";
}

Animal *animalFromDuck = dynamic_cast<Animal


*>(duckFromDuck);
if (animalFromDuck == nullptr) std::cout << "Invalid cast
Animal from Duck!\n";
else animalFromDuck->toString();
Duck *duckFromAnimal = dynamic_cast<Duck *>(animal);
if (duckFromAnimal == nullptr) std::cout << "Invalid cast
Duck from Animal!\n";
else duckFromAnimal->toString();

virtual ~Duck(){
delete name;
}

delete animal;
delete duck;
return 0;
}

private:
std::string *name;
};

Output:
I'm a generic Animal!
I'm a duck and my name
I'm a duck and my name
I'm a duck and my name
Invalid cast Duck from

is Donald!
is Donald!
is Donald!
Animal!

38

Example - References
//

02_03_Dynamic_Cast_References
int main(int argc, const char * argv[]) {
Animal animal = Animal();
Duck duck = Duck();

#include <iostream>
class Animal
{
public:
virtual void toString(){
std::cout << "I'm a generic Animal!\n";
}
};

animal.toString();
duck.toString();
try {
Animal &animalFromDuck = dynamic_cast<Animal &>(duck);
animalFromDuck.toString();
Duck &duckFromAnimal = dynamic_cast<Duck &>(animal);
duckFromAnimal.toString();

class Duck : public Animal


{
public:
Duck() {
name = new std::string("Donald");
}
virtual void toString(){
std::cout << "I'm a duck and my name is " << *name <<
"!\n";
}

} catch (std::exception &e) {


std::cout << "Exception: " << e.what() << "\n";
}
return 0;
}

virtual ~Duck(){
delete name;
}
private:
std::string *name;
};

Output:
I'm a generic Animal!
I'm a duck and my name is Donald!
I'm a duck and my name is Donald!
Exception: std::bad_cast

39

reinterpret_cast <new_type>(expression)

converts any pointer type to any other pointer type

pointers can be to unrelated classes

the result is just a binary copy

it does not perform any checks

can be used to cast pointers to and from integer types

cannot cast away const-ness or volatility


40

Example
//

02_04_Reinterpret_Cast

void ShowDetails(void *pointerToAHumanObject){


Human *human = reinterpret_cast<Human *>(pointerToAHumanObject);
std::cout << "Address value of human after re-cast is:" << human
<< "\n";
human->toString();
}

#include <iostream>
class Animal
{
public:
void toString(){
std::cout << "I'm a generic Animal!\n";
}
};

int main(int argc, const char * argv[]) {


Duck *duck = new Duck();
Human *human = new Human();

class Duck : public Animal


{
public:
Duck() {
name = new std::string("Donald");
}
void toString(){
std::cout << "I'm a duck and my name is " << *name << "!\n";
}

duck->toString();
human->toString();
Animal *animal = reinterpret_cast<Animal *>(duck);
animal->toString();
Animal *anotherAnimal = new Animal();
Duck *anotherDuck = reinterpret_cast<Duck *>(anotherAnimal);
//anotherDuck->toString(); // CRASH!!

virtual ~Duck(){
delete name;
}
private:
std::string *name;
};

anotherDuck = reinterpret_cast<Duck *>(human);


// anotherDuck->toString(); // CRASH
std::cout << "Address value of human is:" << human << "\n";
void *pToHuman = reinterpret_cast<void *>(human);
ShowDetails(pToHuman);

class Human
{
public:
virtual void toString(){
std::cout << "I'm a human and my name is Joe!\n";
}
};

Output:
I'm a duck and my name is Donald!
I'm a human and my name is Joe!
I'm a generic Animal!
Address value of human is:0x1002000b0
Address value of human after re-cast is:0x1002000b0
I'm a human and my name is Joe!

41

delete duck;
delete human;
return 0;

const_cast <new_type>(expression)

it is used to change the const-ness of a pointer

changing the contents of a const pointer after


const_cast-ing it can lead to undefined behaviour

this is the only cast method the can be used to cast


away const-ness or volatility

42

const_cast <new_type>(expression)

Two possibly multilevel pointers to the same type may be converted between each
other, regardless of cv-qualifiers at each level.

lvalue of any type T may be converted to a lvalue or rvalue reference to the same
type T, more or less cv-qualified. Likewise, an rvalue may be converted to a more or
less cv-qualified rvalue reference.

null pointer value may be converted to the null pointer value of new_type

The result will always be:

an lvalue if new_type is an lvalue reference type or an rvalue reference to


function type

an xvalue if new_type is an rvalue reference to object type

a prvalue otherwise
43

Example
//

02_05_Const_Cast
return 0;

#include <iostream>

void printSomethig(const char *toPrint){


std::cout << "I have to print this: " << toPrint << "\n";
}
void printSomthingInUpperCase(char *toPrint){
for (int i = 0; i < strlen(toPrint); ++i){
toPrint[i] = toupper(toPrint[i]);
}
std::cout << "I have to print this in uppercase: " <<
toPrint << "\n";
}
int main(int argc, const char * argv[]) {
const char *someConstString = "Const Random string";
char someNotConstString[] = "Radom string";
printSomethig(someConstString);
printSomethig(const_cast<const
char*>(someNotConstString));
printSomthingInUpperCase(someNotConstString);
//
printSomthingInUpperCase(const_cast<char*>(someConstString));
// CRASH!

Output:
I have to print this: Const Random string
I have to print this: Radom string
I have to print this in uppercase: RADOM STRING

44

typeid

used to check the type of an expression

returns a const reference to a type_info object

type_info objects can be compared with == and !=


operators

typeid uses RTTI to keep track of dynamic objects

if it is applied to an object of a polymorphic class, the


result is the type of the most derived complete object
45

Example
//

02_06_Typeid

std::string getTypeName(const char *name){


int status = -1;
return std::string(abi::__cxa_demangle(name, NULL, NULL,
&status));
}

#include <iostream>
#include <cxxabi.h>
class Animal
{
public:
virtual void toString(){
std::cout << "I'm a generic Animal!\n";
}
};

int main(int argc, const char * argv[]) {


Animal *animal = new Animal();
Duck *duck = new Duck();
Duck &anotherDuck = *duck;
int x = 2;;

class Duck : public Animal


{
public:
Duck() {
name = new std::string("Donald");
}
virtual void toString(){
std::cout << "I'm a duck and my name is " << *name <<
"!\n";
}
virtual ~Duck(){
delete name;
}

std::cout
std::cout
std::cout
"\n";
std::cout

<< typeid(duck).name() << "\n";


<< getTypeName(typeid(animal).name()) << "\n";
<< getTypeName(typeid(anotherDuck).name()) <<
<< getTypeName(typeid(x).name()) << \n";

delete animal;
delete duck;
return 0;
}

private:
std::string *name;
};

Output:
P4Duck
Animal*
Duck
int

46

Logical const and physical const

An object can have a physical state, as well as a logical


state

The logical state is what the user of an object gets

The physical state is the actual object as it resides in the


process memory

A const method should not alter the logical state of an


object

A const method may alter the physical state of an object


47

Mutable objects

The mutable keyword is used to tell the compiler


that a member can be modified even if the object
containing it is const

The mutable keyword can be used to alter the


const-ness of an object

Although it can be done, we SHOULD NOT alter the


logical const-ness of an object

48

Example
//

02_07_Logical_Const

std::cout << "Nr of uses for the sum function is: " <<
addition.numberOfUses() << \n";

#include <iostream>
delete a;
class Addition{
public:
Addition (int a, int b) : a(a), b(b), nrOfUses(0) {};

return 0;
}

int Sum() const {


++nrOfUses;
return a + b;
}
int numberOfUses() const {
return nrOfUses;
}
private:
mutable int nrOfUses;
int a, b;
Addition() {};
};
int main(int argc, const char * argv[]) {
Addition *a= new Addition(3, 4);
const Addition & addition = *a;
std::cout << "Addition result is " << addition.Sum() <<
"\n";

Output:
Addition result is 7
Nr of uses for the sum function is: 1

49

Implicit conversion

Automatically performed when a value is copied to a compatible type

Standard conversions affect fundamental data types and allow


conversion between numerical types

Converting to int from smaller integer types, or from float to double is


called a promotion. It is guaranteed to produce the exact same value
in the destination type

Sometimes, the conversion does not represent the same value exactly:
From negative to unsigned
From/to bool considers false to be 0 (or null, for pointers), and true
for all other values. From bool, true is converted to 1
Floating to integer (the decimal part is discarded)
50

Implicit conversion

Some conversions may imply loss of precision (we usually


get a warning, which we can override with an explicit
conversion)

Array and functions implicitly convert to pointers

Pointers allow the following conversions:


Null pointers can be converted to pointers of any type
Pointers of any type can be converted to void pointers
Upcast: pointers to a derived class can be converted to
a pointer of an accessible and unambiguous base
class, without modifying its const or volatile qualification
51

Implicit conversion for classes

Implicit conversions for classes can be controlled by


3 member functions

Single-Argument constructors allow implicit


conversion from a particular type to initialise an object

Assignment operator allows implicit conversions from


a particular type on assignments

Type-cast operator allows implicit conversion to a


particular type
52

Constructor conversions

C++03: A constructor declared without the function-specifier explicit


that can be called with a single parameter specifies a conversion
from the type of its first parameter to the type of its class. Such a
constructor is called a converting constructor.

C++11: A constructor declared without the function-specifier explicit


specifies a conversion from the types of its parameters to the type of
its class. Such a constructor is called a converting constructor.

An implicit constructor specifies a conversion from the type of its


arguments to the type of its class

Implicitly declared and user defined non-explicit copy constructors


(and move constructors from C++11) are converting constructors.
53

Example
//

02_08_Implicit_Conversion

double f = 2.4f;
IntNumber x;
IntNumber y(2);
IntNumber z = 4;
IntNumber t = f;
int a = x + 2 * y + z;
float b = y + 2.4f;
Point p1 {2, a};
Point p2 = {y, z};
Point p3(p2);

#include <iostream>
class IntNumber {
public:
IntNumber() : value(0) {}
IntNumber(int val) : value(val) {}
operator int() {return value;}
IntNumber & operator=(int val) {value = val; return *this;}
private:
int value;
};

std::cout
std::cout
std::cout
std::cout
std::cout
std::cout
std::cout
std::cout
std::cout

class Point{
public:
Point():x(0),y(0){}
Point(int x, int y):x(x), y(y){}
Point(const Point &p): x(p.x), y(p.y){}
void print(){
std::cout << "(" << x << "," << y << ")\n";
}
private:
int x, y;
};

<<
<<
<<
<<
<<
<<
<<
<<
<<

"x = " << x << "\n";


"y = " << y << "\n";
"z = " << z << "\n";
"t = " << t << "\n";
"a = " << a << "\n";
"b = " << b << "\n";
"p1 = "; p1.print();
"p2 = "; p2.print();
"p3 = "; p3.print();

if (t == 2){
std::cout << "t is 0" << "\n";
}
PrintBoolValue(y);
PrintBoolValue(f);

void PrintBoolValue(bool val){


std::cout << "bool val is " << ((val == true) ? "true" :
"false") << "\n";
}

return 0;

int main(int argc, const char * argv[]) {

Output:
x = 0
y = 2
z = 4
t = 2
a = 8
b = 4.4
p1 = (2,8)
p2 = (2,4)
p3 = (2,4)

t is 0
bool val is true
bool val is true

54

explicit keyword

Defining conversion constructors allows the compiler


to make implicit conversion when constructing an
object

Sometimes we do not want to allow implicit


conversions to be made, as we may want our objects
to be constructed specifically with what we request

The explicit keyword tells the compiler that the


specified conversion cannot be used to perform
implicit conversions
55

Example
//

02_09_Explicit_Keyword

//IntNumber z = 4; // ERROR: No viable conversion from 'int' to


'IntNumber'
//IntNumber t = f; // ERROR: No viable conversion from 'double'
to 'IntNumber'
//int a = x + 2 * y; // ERROR: Invalid operands to binary
expression ('int' and 'IntNumber')
//float b = y + 2.4f; // ERROR: Invalid operands to binary
expression ('IntNumber' and 'float')

#include <iostream>
class IntNumber {
public:
IntNumber() : value(0) {}
explicit IntNumber(int val) : value(val) {}
explicit operator int() {return value;}
IntNumber & operator=(int val) {value = val; return *this;}
private:
int value;
};

//Point p1 {2, x}; // ERROR: No matching constructor for


initialization of 'Point'
//Point p2 = {4, 7}; // ERROR: Chosen constructor is explicit
in copy-initialization
Point p3{3, 9};
Point p4(p3);

class Point{
public:

std::cout
std::cout
std::cout
std::cout

explicit Point():x(0),y(0){}
explicit Point(int x, int y):x(x), y(y){}
explicit Point(const Point &p): x(p.x), y(p.y){}
void print(){
std::cout << "(" << x << "," << y << ")\n";
}
private:
int x, y;
};

<<
<<
<<
<<

"x = " << (int)x << "\n";


"y = " << (int)y << "\n";
"p3 = "; p3.print();
"p4 = "; p4.print();

if ((int)y == 2){
std::cout << "t is 0" << "\n";
}
//PrintBoolValue(y); // ERROR: No matching function for call to
'PrintBoolValue'
PrintBoolValue(f);

void PrintBoolValue(bool val){


std::cout << "bool val is " << ((val == true) ? "true" :
"false") << "\n";
}

int main(int argc, const char * argv[]) {


double f = 2.4f;
IntNumber x;
IntNumber y(2.4f);

Output:
x = 0
y = 2
p3 = (3,9)
p4 = (3,9)
t is 0
bool val is true

56

return 0;

Copy constructor and assignment operator

A copy constructor is a special constructor for a


class/struct that is used to make a copy of an
existing instance

The assignment operator, as the copy constructor


allows making copies of an existing instance

57

Rule of three

Applies prior to C++11

If an objects implements at least one of the


following, it should probably implement all of them:
Destructor
Copy constructor
Copy assignment operator

58

Rule of five

Applies from C++11, due to the move semantics


introduced

If an objects implements at least one of the


following, it should probably implement all of them:
Destructor
Copy constructor
Move constructor
Copy assignment operator
Move constructor operator
59

Example
//

02_10_Rule_Of_Five

}
virtual ~SmartStudent(){
free(name);
}
void printName(){
std::cout << "My name is " << name << "\n";
}
private:
SmartStudent(): name(nullptr){};
char *name;
};

#include <iostream>
class Student{
public:
Student(const char *_name){
name = strdup(_name);
}
virtual ~Student(){
free(name);
}
void printName(){
std::cout << "My name is " << name << "\n";
}
private:
Student(): name(nullptr){};
char *name;
};

int main(int argc, const char * argv[]) {


Student fred("Fred");
SmartStudent barney("Barney");
/* This will cause the app to crash:
{
Student anotherFred = fred;
anotherFred.printName();
}
*/
fred.printName();

class SmartStudent{
public:
SmartStudent(const char *_name){
name = strdup(_name);
}
SmartStudent(const SmartStudent &stud){
name = strdup(stud.name);
}
SmartStudent(SmartStudent &&stud){
name = stud.name;
stud.name = nullptr;
}
SmartStudent & operator=(const SmartStudent &stud){
name = strdup(stud.name);
return *this;
}
SmartStudent & operator=(SmartStudent &&stud){
name = stud.name;
stud.name = nullptr;
return *this;

{
}

SmartStudent barn = barney;


barn.printName();

barney.printName();
}

Output:
x = 0
y = 2
p3 = (3,9)
p4 = (3,9)
t is 0
bool val is true

60

return 0;

Exercise 1

Imagine a banking system with different banks. Each bank has


different branches.

People can belong to a single branch of a bank, but they could


have other accounts at different banks, or at the same bank

People have different amount of money in different accounts


(suppose theres only one currency)

Money can be transferred between accounts

There are different type of customers, some have no transaction


fees, others have fixed transaction fees and others have 1%
transaction fee, depending on their contracts.
61

Day 2

Scope

Static class members

Classes can contain static member data and member


functions

In OOP, if a class contains a static member or a static


member function, it can be accessed without having an
instance of an object of that class. Static member exist
without having objects of a particular class

In C++, static data members are not part of objects

The declaration of a static data member is not considered a


definition. Static data members are declared at class scope,
but defined at file scope. They have external linkage.
64

Example
//

03_01_Static_Members

#include <iostream>
class Animal{
public:
Animal() {nrOfInstances++;}
static int nrOfInstances;
};
int Animal::nrOfInstances = 0;
int main(int argc, const char * argv[]) {
std::cout << "Nr of Animal instances:" <<
Animal::nrOfInstances << "\n";
Animal wolf;
Animal zebra, lion;
std::cout << "Nr of Animal instances:" <<
zebra.nrOfInstances << "\n";
Animal monkey((Animal(lion)));
std::cout << "Nr of Animal instances:" <<
Animal().nrOfInstances << "\n";
return 0;
}

Output:
Nr of Animal instances:0
Nr of Animal instances:3
Nr of Animal instances:4

65

The Singleton Pattern

The Singleton Pattern is a design pattern used to


ensure that a class has only one instance globally
accessible from anywhere within a program

66

The Singleton pattern - disadvantages

They violate the Single Responsibility Principle

They are generally used as global instances

They make the code tightly coupled, so they can be


very hard to fake under testing environments

In multi-threaded environments, they can introduce


bottlenecks and synchronisation problems

67

Diagram

68

Example - pointer to a singleton


//

03_02_A_Singleton_Pattern_Pointers

Logger::getInstance()->logMessage("Log another message


\n");
return 0;
}

#include <iostream>
class Logger{
public:
void logMessage(const char *message){
std::cout << message;
}
static Logger* getInstance(){
if (loggerInstance == nullptr){
loggerInstance = new Logger();
}
return loggerInstance;
}
private:
Logger() {};
Logger (Logger const &);
Logger & operator=(Logger const &);
static Logger *loggerInstance;
};
Logger *Logger::loggerInstance = 0;
int main(int argc, const char * argv[]) {
// insert code here...
Logger::getInstance()->logMessage("Log message\n");

Output:
Log message
Log another message

69

Example - reference to a singleton


//

03_02_B_Singleton_Pattern_References

#include <iostream>
class Logger{
public:
void logMessage(const char *message){
std::cout << message;
}
static Logger& getInstance(){
static Logger loggerInstance;
return loggerInstance;
}
private:
Logger() {};
Logger (Logger const &);
Logger & operator=(Logger const &);
};
int main(int argc, const char * argv[]) {
// insert code here...
Logger::getInstance().logMessage("Log message\n");
Logger::getInstance().logMessage("Log another message\n");
return 0;
}

Output:
Log message
Log another message

70

Nested classes

Nested classes are classes within classes

Nested classes are not visible outside the scope of their enclosing
classes

They can use names, type names, enumerations and names of


static members only from their respective enclosing classes

Nesting a class does not give special access privileges to the


members and methods of the nested class

Friend functions in a nested class are not friend functions for the
enclosing class, so they do not have any special privileges in the
enclosing class
71

Nested class forward declaration

Nested classes can be forward declared and later


defined, either in the same enclosing class, or at file
scope

72

Example
//

03_03_Nested_Classes

#include <iostream>

last = newNode;

}
void printAll(){
std::cout << "All values:";
Node *current = first;
while (current != nullptr) {
std::cout << current->value << " ";
current = current->next;
}
std::cout << "\n";
}
private:
class Node{
public:
int value;
Node *prev, *next;
Node(): prev(nullptr), next(nullptr), value(0) {}
};
Node *first;
Node *last;
};

class DoubleLinkedList{
public:
DoubleLinkedList(): first(nullptr), last(nullptr) {}
virtual ~DoubleLinkedList(){
Node *currentNode = first;
while (currentNode != nullptr) {
Node *nextNode = currentNode->next;
delete currentNode;
currentNode = nextNode;
}
};
void addFront(int value){
Node *newNode = new Node();
newNode->value = value;
newNode->prev = nullptr;
newNode->next = first;
if (first == nullptr){
first = last = newNode;
} else {
first->prev = newNode;
first = newNode;
}
}
void addBack(int value){
Node *newNode = new Node();
newNode->value = value;
newNode->prev = last;
newNode->next = nullptr;
if (last == nullptr){
first = last = newNode;
} else {
last->next = newNode;

int main(int argc, const char * argv[]) {


DoubleLinkedList *list = new DoubleLinkedList();
list->addBack(10);
list->addFront(20);
list->addBack(3);
list->addFront(12);
list->printAll();
delete list;
return 0;
}

Output:
All values:12 20 10 3

73

The Cheshire Cat idiom

Also known as: Opaque Pointer, Pimpl Idiom


(Pointer to Implementation), Compiler Firewall Idiom,
d-pointer

The Bridge Pattern, in Design Patterns

It is a way to hide the implementation details of an


interface from ordinary clients, so that if the
implementation changes, the modules using it dont
need to be recompiled
74

Example
//
//

DoubleLinkedList.hpp
03_04_ Cheshire_Cat

//
//

main.cpp
03_04_ Cheshire_Cat

#ifndef DoubleLinkedList_hpp
#define DoubleLinkedList_hpp

#include <iostream>
#include "DoubleLinkedList.hpp"

#include <iostream>

int main(int argc, const char * argv[]) {


DoubleLinkedList *list = new DoubleLinkedList();
list->addBack(10);
list->addFront(20);
list->addBack(3);
list->addFront(12);
list->printAll();
delete list;
return 0;
}

class DoubleLinkedList{
public:
DoubleLinkedList();
virtual ~DoubleLinkedList();
void addFront(int value);
void addBack(int value);
void printAll();
private:
class Node;
Node *first;
Node *last;
};
#endif /* DoubleLinkedList_hpp */

Output:
All values:12 20 10 3

75

Namespaces

Declarative regions that provide a scope to the identifiers


inside them

They are used to prevent name collisions, as well as to


organise code into logical groups (Eg. std namespace)

At namespace scope, all the identifiers are visible to one


another without qualification

Outside their respective namespaces, the identifiers can


be accessed with the fully qualified name for each (Eg.
std::vector, std::string etc)
76

using declaration

Allows all the names in a namespace to be accessed


without the namespace name as a qualifier

If a local variable has the same name as a


namespace variable, the variable in the namespace
will be hidden

We should place it, if possible, only in .cpp files.


Placing it in header files, discovers the namespace to
other files that might not use it, and may cause name
collisions
77

Example
//

03_05_Namespaces

#include <iostream>
#include <math.h>
namespace RealConstants{
double pi = 3.14159;
double e = 2.71828;
}
namespace GeometricalFigures {
class Circle{
public:
Circle(double r): radius(r){}
double Area(){
using namespace RealConstants;
return pi * radius * radius;
}
private:
Circle();
double radius;
};
}
using namespace std;
int main(int argc, const char * argv[]) {
GeometricalFigures::Circle circle(1);
cout << "Ln(e) = " << log(RealConstants::e) << "\n";
cout << "Area of circle with radius 1: " << circle.Area()
<< "\n";
return 0;
}

Output:
Ln(e) = 0.999999
Area of circle with radius 1: 3.14159

78

Delegation

Object Adapter Pattern

A design pattern that uses composition to make an


adaptation of an existing class to be used from
another interface

In the Object Adapter Pattern, the adapter contains


an instance of the class it adapts

The adapter usually modifies the interface of the


adapted object, in order to resolve compatibility
issues between objects
80

Diagram

81

Example
//

04_01_Object_Adapter_Pattern

StudentAdapter(Student *_student){
student = _student;
}
virtual string toString(){
return string(student->getName());
}
private:
Student *student;
StudentAdapter() {student = nullptr;}
};

#include <iostream>
using namespace std;
class Student{
public:
Student(const char *_name): name(_name){}
const string &getName(){
return name;
}
private:
Student(): name(""){}
string name;
};

int main(int argc, const char * argv[]) {


Student john("John"), michael("Michael"),
andrew("Andrew"), mark("Mark");
Printable* list[4] = {new StudentAdapter(&john), new
StudentAdapter(&michael), new StudentAdapter(&andrew), new
StudentAdapter(&mark)};
PrintList(list, 4);
return 0;
}

class Printable {
public:
virtual string toString(){
return string("");
};
};
void PrintList(Printable* list[], int nrOfValues){
for (int i = 0; i < nrOfValues; ++i){
cout << i << ". " << list[i]->toString() << "\n";
}
}
class StudentAdapter: public Printable{
public:

Output:
0.
1.
2.
3.

John
Michael
Andrew
Mark

82

Null Object Pattern

A Null Object, is in fact, a valid object that does nothing

C++ sometimes needs Null Objects as it does not have


null references

If a method requires a reference to an object and the


caller does not have such an object, nor does he need it,
it can pass a Null Object

However, if a method requires a pointer to an object, the


implementation might (or might not, this must be
documented) accept a null pointer
83

Diagram

84

Example
//

04_02_Null_Object_Pattern

}
};

#include <iostream>
using namespace std;

void PrintList(Student list[], int nrOfValues, PageFormatter


&formatter){
formatter.printHeader();
for (int i = 0; i < nrOfValues; ++i){
cout << i << ". " << list[i].toString() << "\n";
}
formatter.printFooter();
}

class Student{
public:
Student(const char *_name): name(_name){}
string toString(){
return string(name);
}
private:
Student(): name(""){}
string name;
};

int main(int argc, const char * argv[]) {


Student list[4] = {Student("John"), Student("Michael"),
Student("Andrew"), Student("Mark")};
PageFormatter pageFormatter;
NullPageFormatter nullPageFormatter;
cout << "List with format:\n";
PrintList(list, 4, pageFormatter);
cout << "List without format:\n";
PrintList(list, 4, nullPageFormatter);
return 0;
}

class PageFormatter {
public:
virtual void printHeader() {
cout << "===== List header =====\n";
}
virtual void printFooter(){
cout << "===== List footer =====\n";
}
};
class NullPageFormatter: public PageFormatter{
public:
void printHeader() {
}
void printFooter() {

Output:
List with format:
===== List header =====
0. John
1. Michael
2. Andrew
3. Mark
===== List footer =====
List without format:
0. John

1. Michael
2. Andrew
3. Mark

85

Proxy Pattern

We also call proxy objects wrappers

Allows us to provide an interface to other objects, that are


usually harder to interact with

Proxying usually keeps the interface (does not break


inter-operability contracts), but it does some additional
housework for the proxied objects, in order to make them
easier to be used

For example, we can implement: Virtual Proxies, Remote


Proxies, Protection Proxies, Smart References
86

Proxy types

A virtual proxy is a proxy for objects that are expensive


to create. The real object is only created when a client
really needs it.

A remote proxy provides a local representative for an


object that resides in different address space (another
process, another physical system)

A protective proxy controls access to a sensitive


object. It can be used to implement some kind of
permission checking for different uses (like providing
read-only access to an object)
87

Proxy types

A Smart Reference Proxy performs special operation


when an object is accessed. This operations can be:

Loading a persistent object into memory when it


is first used (Lazy Initialisation)

Making the access to an object thread safe

Counting the references to an object so the


resources can be freed when they are no longer
referenced
88

Diagram

89

Example
//

04_03_Proxy_Pattern

}else{
player->addPoints(nrOfAddedPoints);
}

#include <iostream>
using namespace std;

}
int getNrOfPoints(){return player->getNrOfPoints();}
string getName(){return player->getName();}
bool isFemale(){return player->isFemale();}
virtual ~PlayerProxy() {delete player;}
private:
PlayerProxy() {}
Player *player;
};

enum Sex {
Male = 0,
Female = 1
};
class Player{
public:
Player(string _name, Sex _sex): name(_name), sex(_sex),
points(0){}
void addPoints(int nrOfAddedPoints) {points +=
nrOfAddedPoints;}
int getNrOfPoints() {return points;}
string getName(){return name;}
bool isFemale(){ return (sex == Female)? true : false; }
private:
Player() {}
string name;
Sex sex;
int points;
};

int main(int argc, const char * argv[]) {


Player john("John", Male), jane("Jane", Female);
PlayerProxy bob("Bob", Male), alice("Alice",Female);
john.addPoints(10); jane.addPoints(12);
bob.addPoints(5); alice.addPoints(12);
cout << john.getName() << " has " << john.getNrOfPoints() <<
" points\n";
cout << jane.getName() << " has " << jane.getNrOfPoints() <<
" points\n";
cout << bob.getName() << " has " << bob.getNrOfPoints() << "
points\n";
cout << alice.getName() << " has " << alice.getNrOfPoints()
<< " points\n";

class PlayerProxy{
public:
PlayerProxy(string _name, Sex _sex): player(new Player(_name,
_sex)) {}
void addPoints(int nrOfAddedPoints){
if (player->isFemale()){
player->addPoints(nrOfAddedPoints * 2);

Output:
John has 10 points
Jane has 12 points
Bob has 5 points
Alice has 24 points

90

return 0;

Overloading the member access operator (->)

The member access operator is a unary operator

The function must be a non static member function

The return value is used to perform the member lookup

The return value must be a pointer. If, the return value


is an object of class type, and not a pointer, the
subsequent -> operator will be called, and so on (this
is called a drill-down behaviour), until a pointer is
return.
91

Example
//

04_04_Overloading_Member_Access_Operator

#include <iostream>

player->addPoints(nrOfAddedPoints);

}
Player* operator->(){
return player;
}

using namespace std;


enum Sex {
Male = 0,
Female = 1
};

virtual ~PlayerProxy() {delete player;}


private:
PlayerProxy() {}
Player *player;
};

class Player{
public:
Player(string _name, Sex _sex): name(_name), sex(_sex),
points(0){}
void addPoints(int nrOfAddedPoints) {points +=
nrOfAddedPoints;}
int getNrOfPoints() {return points;}
string getName(){return name;}
bool isFemale(){ return (sex == Female)? true : false; }
private:
Player() {}
string name;
Sex sex;
int points;
};

int main(int argc, const char * argv[]) {


Player john("John", Male), jane("Jane", Female);
PlayerProxy bob("Bob", Male), alice("Alice",Female);
john.addPoints(10); jane.addPoints(12);
bob->addPoints(5); alice.addPoints(12); alice->addPoints(10);
cout << john.getName() << "
points\n";
cout << jane.getName() << "
points\n";
cout << bob->getName() << "
points\n";
cout << alice->getName() <<
<< " points\n";
return 0;
}

class PlayerProxy{
public:
PlayerProxy(string _name, Sex _sex): player(new Player(_name,
_sex)) {}
void addPoints(int nrOfAddedPoints){
if (player->isFemale()){
player->addPoints(nrOfAddedPoints * 2);
}else{

Output:
John has 10 points
Jane has 12 points
Bob has 5 points
Alice has 34 points

92

has " << john.getNrOfPoints() << "


has " << jane.getNrOfPoints() << "
has " << bob->getNrOfPoints() << "
" has " << alice->getNrOfPoints()

Smart pointers

A Smart Pointer is an abstract data type that simulates a pointer


while providing additional features (memory management,
bounds checking etc)

They prevent the most situations of memory leaks, by


automatically deallocating the memory

Some keep track of references (they clean up when no-one


references then) others of ownership (they clean up when the
owner gets destroyed).

From C++11 on, std (through the header <memory>) contains


implementations of different types smart pointers (unique, shared
or weak)
93

Example
//

04_05_Smart_Pointers

operator Student*(){
return student;
}

#include <iostream>
using namespace std;

private:
StudentPtr() {};
Student *student;
};

class Student{
public:
Student(const char *_name): name(_name){}
const string &getName(){
return name;
}
private:
Student(): name(""){}
string name;
};

void PrintStudentName(Student *student){


cout << "The name is " << student->getName() << "\n";
}
int main(int argc, const char * argv[]) {
Student *john = new Student("John");
StudentPtr jane = StudentPtr(new Student("Jane")), jill =
StudentPtr(new Student("Jill"));

class StudentPtr{
public:
StudentPtr(Student *student_) {
student = student_;
}
~StudentPtr(){
delete student;
}
Student &operator*(){
return *student;
}
Student *operator->(){
return student;
}

PrintStudentName(john);
PrintStudentName(jane);
PrintStudentName(jill);
delete john;
return 0;
}

Output:
The name is John
The name is Jane
The name is Jill

94

Smart references

A smart reference, like a smart pointer is a class


containing a pointer as its only data member

Unlike smart pointers, smart references are


manipulated directly

They do a much better job of implementation hiding.


While the smart pointer must be initialised from an
actual pointer, which can always be accessed
through the operator ->, the internals of a smart
references are always protected from user access
95

Example
//

04_06_Smart_References

const string &getName(){


return name;
}
private:
Student(): name(""){}
string name;
};

#include <iostream>
using namespace std;
class StudentRef{
public:
StudentRef(const char *name) {
student = new Student(name);
}
~StudentRef(){
delete student;
}
StudentRef(const StudentRef & studentRef){
student = new Student((studentRef.student)>getName().c_str());
}
StudentRef& operator= (const StudentRef &studentRef){
delete student;
student = new Student((studentRef.student)>getName().c_str());
return *this;
}
const string &getName(){
return student->getName();
}
private:
class Student{
public:
Student(const char *_name): name(_name){}

StudentRef() {};
Student *student;
};
int main(int argc, const char * argv[]) {
StudentRef john("John");
cout << "My name is: " << john.getName() << "\n";
return 0;
}

Output:
My name is: John

96

Template Method Pattern

A design pattern used to standardise the


implementation of a particular algorithm

The Template Method Pattern lets the subclasses


redefine certain steps of the algorithm without
changing its whole structure

97

Diagram

98

Example
//

04_07_Template_Method_Pattern

class ScpDownloader: public Downloader{


public:
ScpDownloader(string privateKey_): privateKey(privateKey_)
{};
private:
ScpDownloader() {};
virtual void Connect(){
cout << "Connecting to SCP server using private key " <<
privateKey << "...\n";
}
virtual void DownloadFile(){
cout << "Downloading file from SCP server...\n";
cout << "Download finished\n";
}
virtual void Disconnect(){
cout << "Disconnecting from SCP server...\n";
}
string privateKey;
};

#include <iostream>
using namespace std;
class Downloader{
public:
void Download(){
Connect();
DownloadFile();
Disconnect();
}
private:
virtual void Connect() {};
virtual void DownloadFile() {};
virtual void Disconnect() {}
};
class HttpDownloader: public Downloader{
private:
virtual void Connect(){
cout << "Connecting to HTTP server...\n";
}
virtual void DownloadFile(){
cout << "Downloading file from HTTP server...\n";
cout << "Download finished\n";
}
virtual void Disconnect(){
cout << "Disconnecting from HTTP server...\n";
}
};

int main(int argc, const char * argv[]) {


Downloader *httpDownloader = new HttpDownloader();
Downloader *scpDownloader = new ScpDownloader("testkey123");
httpDownloader->Download();
scpDownloader->Download();

Output:
Connecting to HTTP server...
Downloading file from HTTP server...
Download finished
Disconnecting from HTTP server...
Connecting to SCP server using private key testkey123...
Downloading file from SCP server...
Download finished
Disconnecting from SCP server...

99

delete httpDownloader;
delete scpDownloader;
return 0;

Factory objects

Factory objects are objects responsible with the


creation of other objects

Sometimes, the creation of an object can be so


complicated that another object must handle it (to
respect the Single Responsibility Principle)

100

Diagram

101

Example
//

04_08_Factory_Objects

};

#include <iostream>

class DeviceBuilder{
public:
Computer *getDevice(DeviceType deviceType){
switch (deviceType) {
case DeviceType::DTSmartphone:
return new Smartphone();
break;
case DeviceType::DTIPhone:
return new IPhone("white");
break;
case DeviceType::DTTablet:
return new Tablet(10.1f);
default:
return new Computer();
}
}
};

using namespace std;


enum DeviceType{ DTComputer = -1, DTSmartphone
DTTablet };

= 0, DTIPhone,

class Computer {
public:
virtual void whatAmI(){ cout << "I'm a computer\n"; }
};
class Smartphone: public Computer{
public:
virtual void whatAmI(){ cout << "I'm a smartphone\n"; }
};
class IPhone: public Smartphone {
public:
IPhone(string color_): color(color_) {}
virtual void whatAmI(){ cout << "I'm a " << color << " iPhone\n";
}
private:
IPhone(){}
string color;
};

int main(int argc, const char * argv[]) {


DeviceBuilder builder;
Computer *iPhone = builder.getDevice(DeviceType::DTIPhone);
Computer *computer = builder.getDevice(DeviceType::DTComputer);
Computer *tablet = builder.getDevice(DeviceType::DTTablet);
Computer *smartphone =
builder.getDevice(DeviceType::DTSmartphone);

class Tablet: public Computer {


public:
Tablet(double diagonalSize_): diagonalSize(diagonalSize_) {}
virtual void whatAmI(){ cout << "I'm a tablet with a "<<
diagonalSize << " diagonal\n"; }
private:
Tablet() {}

smartphone->whatAmI(); tablet->whatAmI();computer>whatAmI();iPhone->whatAmI();
delete iPhone; delete computer; delete tablet; delete smartphone;
}

Output:
I'm
I'm
I'm
I'm

a
a
a
a

double diagonalSize;

smartphone
tablet with a 10.1 diagonal
computer
white iPhone

102

return 0;

Subscripting

Overloading the subscript operator ([])

The subscript operator is uses to access array


elements

We sometimes need to do extra checking before


accessing array elements (bounds checking,
availability, privilege etc)

104

Example
//
//

DoubleLinkedList.hpp
05_01_Overloading_Subscript_Operator

#ifndef DoubleLinkedList_hpp
#define DoubleLinkedList_hpp

throw "Index of bounds!";


}
return currentNode->value;

const int &DoubleLinkedList::operator[] (const int index) const{


return this->operator[](index);
}

class DoubleLinkedList{
public:
DoubleLinkedList();
virtual ~DoubleLinkedList();
void addFront(int value);
void addBack(int value);
void printAll();

//
//

main.cpp
05_01_Overloading_Subscript_Operator

#include <iostream>
#include "DoubleLinkedList.hpp"
using namespace std;

int &operator[] (const int index);


const int &operator[] (const int index) const;
private:
class Node;
Node *first;
Node *last;
};

int main(int argc, const char * argv[]) {


DoubleLinkedList list;
list.addBack(10);
list.addFront(20);
list.addBack(3);
list.addFront(12);
list.printAll();
cout << "The third element in list is: " << list[2] << "\n";
list[1] = 40;
list.printAll();
list[17] = 24;
return 0;
}

#endif /* DoubleLinkedList_hpp */
//DoubleLinkedList.cpp
int & DoubleLinkedList::operator[] (const int index){
int i = 0;
Node *currentNode = first;
while (currentNode != nullptr) {
if (i == index){
break;
}
currentNode = currentNode->next;
++i;
}
if (currentNode == nullptr){

Output:
All values:12 20 10 3
The third element in list is: 10
All values:12 40 10 3
libc++abi.dylib: terminating with uncaught exception of type
char const*

105

Multi-dimensional subscripting

Multi-dimensional subscripting is possible by


overloading the subscript operator, having it return
another object that allows subscripting (another class
with the subscript operator overloaded or an actual
array)

For multidimensional data structures (Matrixes)


overloading the () operator is preferred to returning an
array from the [] operator

The operator [] only takes one parameter, while the


operator () takes any number of parameters
106

Example
//
//

DoubleLinkedList.hpp
05_02_Multidimensional_Subscripting

#ifndef DoubleLinkedList_hpp
#define DoubleLinkedList_hpp

class DoubleLinkedList{
public:
DoubleLinkedList();
virtual ~DoubleLinkedList();
void addFront(std::string value);
void addBack(std::string value);
void printAll();

if (currentNode == nullptr){
throw "Index of bounds!";
}
return currentNode->value;

const string &DoubleLinkedList::operator[] (const int index) const{


return this->operator[](index);
}
// main.cpp
// 05_02_Multidimensional_Subscripting
#include <iostream>
#include "DoubleLinkedList.hpp"

std::string &operator[] (const int index);


const std::string &operator[] (const int index) const;
private:
class Node;
Node *first;
Node *last;
};

using namespace std;


int main(int argc, const char * argv[]) {
DoubleLinkedList list;
list.addBack("10");
list.addFront("20");
list.addBack("3");
list.addFront("12");
list.printAll();
cout << "The third element in list is: " << list[2] << "\n";
cout << "The first letter in the second element is " << list[1]
[0] << "\n";
list[1] = "40";
list[0][1] = "A"[0];
list.printAll();
list[17] = "24";
return 0;
}

#endif /* DoubleLinkedList_hpp */
//DoubleLinkedList.cpp
string & DoubleLinkedList::operator[] (const int index){
int i = 0;
Node *currentNode = first;
while (currentNode != nullptr) {
if (i == index){
break;
}
currentNode = currentNode->next;
++i;
}

Output:
All values:12 20 10 3
The third element in list is: 10
The first letter in the second element is 2
All values:1A 40 10 3
libc++abi.dylib: terminating with uncaught exception of type
char const*

107

Associative arrays

Also known as maps, symbol tables, dictionaries

They are are abstract data types composed of a


collection of pairs (key, value) such that each key
appears only once in the collection

The pairs can be added, removed, modified or


looked up

Associative containers are ordered associative


arrays
108

Example
//
//

DoubleLinkedDict.hpp
05_03_Associative_Arrays

#ifndef DoubleLinkedDict_hpp
#define DoubleLinkedDict_hpp

class DoubleLinkedDict{
public:
DoubleLinkedDict();
virtual ~DoubleLinkedDict();
void addFront(const std::string & key, int value);
void addBack(const std::string & key, int value);
void printAll();

if (currentNode == nullptr){
addBack(index, 1);
return last->value;
}
return currentNode->value;

const int & DoubleLinkedDict::operator[] (const string & index)


const{
return this->operator[](index);
}
// main.cpp
// 05_03_Associative_Arrays

int &operator[] (const std::string& index);


const int &operator[] (const std::string& index) const;
private:
class Node;
Node *first;
Node *last;
};

#include <iostream>
#include "DoubleLinkedDict.hpp"
using namespace std;
int main(int argc, const char * argv[]) {
DoubleLinkedDict age;
age.addBack("John", 10);
age.addFront("Jane", 20);
age.addBack("Mike", 30);
age.addFront("Luke", 15);
age.printAll();
cout << "Mike's age is " << age["Mike"] << "\n";
age["Alice"] = 21;
age.printAll();
return 0;
}

#endif /* DoubleLinkedDict_hpp */
// DoubleLinkedDict.cpp
int & DoubleLinkedDict::operator[] (const string & index){
Node *currentNode = first;
while (currentNode != nullptr) {
if (currentNode->key == index){
break;
}
currentNode = currentNode->next;
}

Output:
All values:
Luke: 15
Jane: 20
John: 10
Mike: 30
Mike's age is 30
All values:
Luke: 15
Jane: 20

John: 10
Mike: 30
Alice: 21

109

Exercise 2

Use the banking system created yesterday

Implement a national bank that decides debts and has all banks registered to it

Hide the transaction (make it nested in one of the other classes and hide its
implementation)

Implement a null client

Add debit and credit cards

Use clients from smart pointers or references

Improve the method by which the contracts are created (use a factory method)

Implement subscripting and an associative array in the national bank for getting the
information about any of the banks in the system
110

Day 3

Template functions

Using template functions in generic algorithms

Template functions allows us to specify a set of


functions base on the same code that can work with
different types, or classes

Generic algorithms are a great place to use


template functions (or classes) as the same
algorithm can usually be applied to a great set of
types. For example, different algorithms for sorting
can be applied to anything, from number types to
more abstract classes
113

Example
//

06_01_Using_Template_Functions

int intArray[] = {0, -2, 5, 2, 7};


float floatArray[] = {0.4f, 7, 4, 1.6f};
printList(intArray, 5);
printList(floatArray, 4);
// printList(string("asdsa")), 2); //ERROR: No matching
function for call to 'printList'
Sort(intArray, 5);
Sort(floatArray, 4);
printList(intArray, 5);
printList(floatArray, 4);
return 0;
}

#include <iostream>
using namespace std;
template <typename T>
void Sort(T array[], int length){
bool sorted = false;
while (!sorted){
sorted = true;
for (int i = 0; i < (length - 1); ++i){
if (array[i] > array[i+1]) {
T aux = array[i];
array[i] = array[i+1];
array[i+1] = aux;
sorted = false;
}
}
}
}
template <typename T>
void printList(T array[], int length){
cout << "Array: [";
for (int i = 0; i < length - 1; ++i){
cout << array[i] << ", ";
}
cout << array[length-1] << "]\n";
}
int main(int argc, const char * argv[]) {

Output:
Array:
Array:
Array:
Array:

[0, -2, 5, 2, 7]
[0.4, 7, 4, 1.6]
[-2, 0, 2, 5, 7]
[0.4, 1.6, 4, 7]

114

Specialising template functions

Template specialisation lets templates deal with


special cases

There are cases when algorithms are much more


efficient for a certain kind of sequence

We might also want to specialise a template if some


types (or classes) dont comply to a known
interface, used for the template

115

Example
//

06_02_Specialising_Template_Functions

#include <iostream>
using namespace std;

template <typename T>


void Sort(T array[], int length){
bool sorted = false;
while (!sorted){
sorted = true;
for (int i = 0; i < (length - 1); ++i){
if (array[i] > array[i+1]) {
T aux = array[i];
array[i] = array[i+1];
array[i+1] = aux;
sorted = false;
}
}
}
}

template <typename T>


void printList(T array[], int length){
cout << "Array: [";
for (int i = 0; i < length - 1; ++i){
cout << array[i] << ", ";
}
cout << array[length-1] << "]\n";
}
int main(int argc, const char * argv[]) {
int intArray[] = {0, -2, 5, 2, 7};
float floatArray[] = {0.4f, 7, 4, 1.6f};
char *stringArray[] = {"test", "abc", "jack"};
printList(intArray, 5);
printList(floatArray, 4);
printList(stringArray, 3);
Sort(intArray, 5);
Sort(floatArray, 4);
Sort(stringArray, 3);
printList(intArray, 5);
printList(floatArray, 4);
printList(stringArray, 3);
return 0;
}

template<>
void Sort(char* array[], int length){
bool sorted = false;
while (!sorted){
sorted = true;
for (int i = 0; i < (length - 1); ++i){
if (strcmp(array[i], array[i+1]) > 0) {
char* aux = array[i];
array[i] = array[i+1];
array[i+1] = aux;
sorted = false;
}

Output:
Array:
Array:
Array:
Array:
Array:
Array:

[0, -2, 5, 2, 7]
[0.4, 7, 4, 1.6]
[test, abc, jack]
[-2, 0, 2, 5, 7]
[0.4, 1.6, 4, 7]
[abc, jack, test]

116

Overloading template functions

Template functions can be overloaded, just like normal


functions

A non-template function is always distinct from a template


specialisation with the same type

Specialisations of different function templates are always


distinct from each other even if they have the same type

Two function templates with the same return type and the
same parameter list are distinct and can be distinguished
with explicit template argument list
117

Example
//

06_03_Overloading_Template_Functions

void f( int, double ){


cout << "I was called: " << __PRETTY_FUNCTION__ << "\n";
}
void f( int ){
cout << "I was called: " << __PRETTY_FUNCTION__ << "\n";
}

#include <iostream>
using namespace std;
template<typename T1, typename T2>
void f( T1, T2 ){
cout << "I was called: " << __PRETTY_FUNCTION__
}
template<typename T> void f( T )
{
cout << "I was called: " << __PRETTY_FUNCTION__
}
template<typename T> void f( T, T ){
cout << "I was called: " << __PRETTY_FUNCTION__
}
template<typename T> void f( T* ){
cout << "I was called: " << __PRETTY_FUNCTION__
}
template<typename T> void f( T*, T ){
cout << "I was called: " << __PRETTY_FUNCTION__
}
template<typename T> void f( T, T* ){
cout << "I was called: " << __PRETTY_FUNCTION__
}
template<typename T> void f( int, T* ){
cout << "I was called: " << __PRETTY_FUNCTION__
}
template<> void f<int>( int ){
cout << "I was called: " << __PRETTY_FUNCTION__
}

<< "\n";

int main(int argc, const char * argv[]) {


int
i;
double
d;
float
ff;
class C{} c;

<< "\n";

cout << "Calling


cout << "Calling
cout << "Calling
cout << "Calling
cout << "Calling
cout << "Calling
cout << "Calling
cout << "Calling
cout << "Calling
cout << "Calling
cout << "Calling
cout << "Calling
return 0;

<< "\n";
<< "\n";
<< "\n";
<< "\n";
<< "\n";

f( i ) ... \n";
f<int>( i ) ... \n";
f( i, i ) ... \n";
f( c ) ... \n";
f( i, ff ) ... \n";
f( i, d ) ... \n";
f( c, &c ) ... \n";
f( i, &d ) ... \n";
f( &d, d ) ... \n";
f( &d ) ... \n";
f( d, &i ) ... \n";
f( &i, &i ) ... \n";

<< "\n";

Output:
Calling f( i ) ...
I was called: void f(int)
Calling f<int>( i ) ...
I was called: void f(int)
Calling f( i, i ) ...
I was called: void f(T, T) [T = int]
Calling f( c ) ...
I was called: void f(T) [T = C]
Calling f( i, ff ) ...
I was called: void f(T1, T2) [T1 = int, T2 = float]
Calling f( i, d ) ...
I was called: void f(int, double)
Calling f( c, &c ) ...

I was called: void f(T, T *) [T = C]


Calling f( i, &d ) ...
I was called: void f(int, T *) [T = double]
Calling f( &d, d ) ...
I was called: void f(T *, T) [T = double]
Calling f( &d ) ...
I was called: void f(T *) [T = double]
Calling f( d, &i ) ...
I was called: void f(T1, T2) [T1 = double, T2 = int *]
Calling f( &i, &i ) ...
I was called: void f(T, T) [T = int *]

118

f( i );
f<int>( i );
f( i, i );
f( c );
f( i, ff );
f( i, d );
f( c, &c );
f( i, &d );
f( &d, d );
f( &d );
f( d, &i );
f( &i, &i );

Template instantiation and linkage

The compiler instantiates templates when they are


used, by substituting the template type with the
actual type that is used. Each instantiation is a
version of the template specialised for that type

We use it if we need to export template from a


library, as uninstantiated templates are not put into
object files

At linkage, only one copy of the instantiation will end


up in the executable file
119

Example
//
//

Templates.hpp
06_04_Template_Instantiation

}
template <typename T>
void printList(T array[], int length){
cout << "Array: [";
for (int i = 0; i < length - 1; ++i){
cout << array[i] << ", ";
}
cout << array[length-1] << "]\n";
}

#ifndef Templates_hpp
#define Templates_hpp
template <typename T>
void Sort(T array[], int length);
template <typename T>
void printList(T array[], int length);

template
template
template
template

#endif /* Templates_hpp */
//
//

Templates.cpp
06_04_Template_Instantiation

//
//

#include <iostream>
#include "Templates.hpp"

Sort(int array[], int length);


Sort(float array[], int length);
printList(int array[], int length);
printList(float array[], int length);

main.cpp
06_04_Template_Instantiation

#include <iostream>
#include "Templates.hpp"

using namespace std;


template <typename T>
void Sort(T array[], int length){
bool sorted = false;
while (!sorted){
sorted = true;
for (int i = 0; i < (length - 1); ++i){
if (array[i] > array[i+1]) {
T aux = array[i];
array[i] = array[i+1];
array[i+1] = aux;
sorted = false;
}
}
}

int main(int argc, const char * argv[]) {


int intArray[] = {0, -2, 5, 2, 7};
float floatArray[] = {0.4f, 7, 4, 1.6f};
printList(intArray, 5);
printList(floatArray, 4);
Sort(intArray, 5);
Sort(floatArray, 4);
printList(intArray, 5);
printList(floatArray, 4);
return 0;
}

Output:
Array:
Array:
Array:
Array:

void
void
void
void

[0, -2, 5, 2, 7]
[0.4, 7, 4, 1.6]
[-2, 0, 2, 5, 7]
[0.4, 1.6, 4, 7]

120

Template classes

Using template classes for generic types

There are a lot of cases when objects act the same,


no matter what type of data they contain

Like template functions, template classes can be


defined to make them more abstract, or
independent of the datatype(s) used

Template classes can also be specialised and


instantiated

122

Example
//

07_01_Template_Classes_For_Generic_Types

#include <iostream>
using namespace std;
template <typename T>
class Number{
public:
Number(T value_): value(value_) {}
operator T() {return value;}
private:
Number(){}
T value;
};
int main(int argc, const char * argv[]) {
Number<int> intNr(7);
Number<float> floatNr(4.5f);
int sum = intNr + floatNr;
cout << "intNr = " << intNr << "\n";
cout << "floatNr = " << floatNr << "\n";
cout << "sum = " << sum << "\n";
return 0;
}

Output:
intNr = 7
floatNr = 4.5
sum = 11

123

Multiple template parameters

For multiple template parameters, template classes can also be


partially specialised

Template classes instantiation, however, cannot be partial, only


explicit

When a class template is instantiated, and there are partial


specialisations available the compiler has to decide if the primary
template is to be used, or one of the specialisations:
1. If only one specialisation matches the template arguments,
then that specialisation is used
2. If more than one specialisation matches, the most specialised
one is used, if it is unique. If it is not unique, the code cannot
be compiled
3. If no specialisations match, then the primary template is used
124

Example
//

07_02_Multiple_Template_Parameters

#include <iostream>
using namespace std;
template <typename T1, typename T2>
class Pair{
public:
T1 first;
T2 second;
Pair(T1 first_, T2 second_): first(first_),
second(second_) {}
void Print(){
cout << "(" << first << ", " << second << ")\n";
}
};
int main(int argc, const char * argv[]) {
Pair<int, float> pair1(1, 1);
Pair<string, int> pair2("test", 3);
pair1.Print();
pair2.Print();
return 0;
}

Output:
(1, 1)
(test, 3)

125

std::vector

Sequence container representing an array that can change


in size

They use contiguous storage locations for their elements. If


it needs to grow in size, they might need to be re-allocated

They allocate some extra storage in case the size might


grow

They are very fast at accessing elements, as well as at


adding or removing elements from the end. For any other
operations, they are slower than the other containers
126

Example
//

07_03_Vector

#include <iostream>
#include <vector>
using namespace std;
int main(int argc, const char * argv[]) {
vector<int> v;
v.push_back(10); v.push_back(14);
cout << "Max size is: " << v.max_size() << "\n";
cout << "Capacity is: " << v.capacity() << "\n";
cout << "The elements are: ";
for (int i = 0; i < v.size(); i++) {
cout << v[i] << " ";
}
cout << "\n";
return 0;
}

Output:
Max size is: 4611686018427387903
Capacity is: 2
The elements are: 10 14

127

std::list

Lists are sequence containers that allow constant time


for adding and removing elements anywhere in the
sequence

They are implemented as doubly-linked lists


(std::forward_list is a very similar container, being
implemented as a single-linked list, thus having only a
forward iterator)

They perform better in inserting and removing


elements than other containers. However, they lack
direct access to elements by their position.
128

Example
//

07_04_List

#include <iostream>
#include <list>
using namespace std;
void printList(list<float> l){
cout << "List is: ";
for (auto it = l.begin(); it!=l.end(); it++){
cout << *it << " ";
}
cout << "\n";
}
int main(int argc, const char * argv[]) {
list<float> l;
l.push_back(10); l.push_back(14);l.push_front(24);
cout << "Max size is: " << l.max_size() << "\n";
printList(l);
l.reverse();
printList(l);
return 0;
}

Output:
Max size is: 9223372036854775807
List is: 24 10 14
List is: 14 10 24

129

std::pair

A simple container that represents a tuple

The two values can be of different types and can be


accessed directly

130

Example
//

07_05_Pair

#include <iostream>
#include <utility>
using namespace std;
int main(int argc, const char * argv[]) {
pair <string, int> pair1("James", 4);
pair <int, float> pair2 = make_pair(10, 4.2);
cout << "Pair 1: ( " << pair1.first << ", " <<
pair1.second << ")\n";
cout << "Pair 2: ( " << pair2.first << ", " <<
pair2.second << ")\n";
return 0;
}

Output:
Pair 1: ( James, 4)
Pair 2: ( 10, 4.2)

131

std::map

They are associative containers that bind a key and a


value

The key values must be unique, and they are used to


access and sort the elements

The elements in a map are always sorted be their key


value

They perform very well at accessing elements by their key

They are usually binary search trees


132

Example
//

07_06_Map

#include <iostream>
#include <map>
using namespace std;
void printMap(map<string, int> aMap){
cout << "Map is:\n";
for (auto it = aMap.begin(); it != aMap.end(); it++){
cout << it->first << ": " << it->second << "\n";
}
}
int main(int argc, const char * argv[]) {
map<string, int> age;
age.insert(make_pair("Jane", 10));
age.insert(pair<string, int>("Mike", 21));
age["July"] = 12;
cout << "Max size: " << age.max_size() << "\n";
cout << "Size: " << age.size() << "\n";
cout << "Mike's age: " << age["Mike"] << "\n";
printMap(age);
return 0;
}

Output:
Max size: 288230376151711743
Size: 3
Mike's age: 21
Map is:
Jane: 10
July: 12
Mike: 21

133

Functional abstraction

Traditional callbacks with function pointers

Every function is in fact a pointer. It points to the


code where the function is defined (or implemented)

A callback is a function that should be called when


something happens

135

Example
//

13_01_Traditional_Callbacks

int main(int argc, const char * argv[]) {


Sleep(2, finishSleeping);
GoToSleep(4, wakeUp);
return 0;
}

#include <iostream>
using namespace std;
typedef void (*finishSleeping_fn)(int);
void finishSleeping(int interval){
cout << "I slept for " << interval << " seconds.\n";
}
void wakeUp(int sleepInterval){
cout << "Good morning! I slept for " << sleepInterval << "
seconds.\n";
}
void Sleep(int interval, finishSleeping_fn onFinishSleeping){
cout << "Will sleep for " << interval << " seconds...\n";
sleep(interval);
onFinishSleeping(interval);
}
void GoToSleep(int interval, void(*onWake)(int)){
cout << "Good night! I am going to sleep for " << interval
<< " seconds...zzz\n";
sleep(interval);
onWake(interval);
}

Output:
Will sleep for 2 seconds...
I slept for 2 seconds.
Good night! I am going to sleep for 4 seconds...zzz
Good morning! I slept for 4 seconds.

136

Member function pointers

Member functions can be passes as arguments and


used as callbacks

To be able to access a member function, we have to


have an object (an instance of the class that defines
the method)

137

Example
//

13_02_Memeber_Function_Pointers

return 0;
}

#include <iostream>
using namespace std;
#define CALL_MEMBER_FN(object,ptrToMember)
((object).*(ptrToMember))
class SleepHandler{
public:
void finishSleeping(int interval){
cout << "I slept for " << interval << " seconds.\n";
}
};
typedef void (SleepHandler::*sleepHandlerFn)(int);
void Sleep(int interval, SleepHandler &sleepHandler,
sleepHandlerFn onFinishSleeping){
cout << "Will sleep for " << interval << " seconds...\n";
sleep(interval);
CALL_MEMBER_FN(sleepHandler, onFinishSleeping)(interval);
//(sleepHandler.*onFinishSleeping)(interval); // we can
also do this
}
int main(int argc, const char * argv[]) {
SleepHandler sleepHandler;
Sleep(2, sleepHandler, &SleepHandler::finishSleeping);

Output:
Will sleep for 2 seconds...
I slept for 2 seconds.

138

Command Pattern

The Command Pattern is basically an object


oriented callback, which encapsulates a request as
an object

It is used to issue requests to objects without


knowing how the requests will be handled

It decouples the object the performs the operation


from the one that invokes it

139

Example
//

13_03_Command_Pattern

#include <iostream>
#include <list>

private:
Square *obj;
Action action;
double param;
};

using namespace std;


class Square {
public:
Square(double sideLength_): sideLength(sideLength_){}
double Area() {return sideLength * sideLength;}
void Rotate(double angle) { cout << "Rotating by " << angle <<
" degrees\n";}
void Resize(double factor) {
cout << "Resizing by " << factor << "\n";
sideLength *= factor;
}
void Draw(){
cout << "Drawing a square shape with the size length of " <<
sideLength << " and an area of " << Area() << "\n";
}
virtual ~Square() {}
private: double sideLength;
};

class Transformation{
public:
void AddCommand(Command *c){
commandList.push_back(c);
}
void Apply(){
auto it = commandList.begin();
while (it != commandList.end()){
(*it++)->execute();
}
}
private:
list<Command *> commandList;
};
int main(int argc, const char *
Square *s = new Square(4);
s->Draw();
Transformation t;
t.AddCommand(new Command(s,
t.AddCommand(new Command(s,
t.AddCommand(new Command(s,
t.Apply();
s->Draw();
delete s;
return 0;
}

class Command{
public:
typedef void (Square::*Action)(double);
Command(Square *obj_, Action action_, double param_){
obj = obj_;
action = action_;
param = param_;
}
void execute(){
(obj->*action)(param);

Output:
Drawing a square shape with the size length of 4 and an area of 16
Resizing by 2
Rotating by 45 degrees
Resizing by 4
Drawing a square shape with the size length of 32 and an area of 1024

140

argv[]) {

&Square::Resize, 2));
&Square::Rotate, 45));
&Square::Resize, 4));

Template techniques

Templating on precision

When using templates, we might lose precision when


intermediary values in some algorithms might be
greater then the types the algorithm was designed for

For example, to calculate the arithmetic mean for


some int or floats, we might need extra space to store
the sum (before dividing to the count), so a long or a
double might accommodate those intermediary values

The problem is that we cannot know the supertype


of the template
142

Example
//

16_01_Templating_On_Precision

};

#include <iostream>

int main(int argc, const char * argv[]) {


short am = arithmeticMean<short>(30000, 10000);
short smartAm = smartArithmeticMean<short>(30000, 10000);
cout << "am = " << am << "\n";
cout << "smartAm = " << smartAm << "\n";
return 0;
}

using namespace std;


template <typename T>
struct long_type;
template <typename T>
float arithmeticMean(T a, T b){
T sum = a + b;
return sum / 2;
}
template <typename T>
float smartArithmeticMean(T a, T b){
typename long_type<T>::type sum = a + b;
return sum / 2;
}
template<> struct long_type<short>{
typedef int type;
};
template<> struct long_type<int>{
typedef long type;
};
template<> struct long_type<float>{
typedef double type;

Output:
am = -12768
smartAm = 20000

143

Template adapters

Using templates, we can adapt similar classes to a


more clear interface

STL uses template adapters a lot: the


std::reverse_iterator (adapts an iterator by reversing
its motion), the std:stack (adapts a container to
provide a simple stack interface) and so on

144

Example
//

16_02_Template_Adapters

class PrintableAdapter: public Printable {


public:
PrintableAdapter()=delete;
PrintableAdapter(AdapteeType *obj_, string(AdapteeType::*toString_)()){
object = obj_;
toStringMethod = toString_;
}
~PrintableAdapter(){delete object;}
string toString(){
return (object->*toStringMethod)();
}
private:
AdapteeType *object;
string (AdapteeType::*toStringMethod)();
};
int main(int argc, const char * argv[]) {
Student john("John"), michael("Michael"), andrew("Andrew"),
mark("Mark");
Circle circle1(3), circle2(1.5), circle3(10);
Printable* list[7] = {
new PrintableAdapter<Student>(&john, &Student::getName),
new PrintableAdapter<Student>(&michael, &Student::getName),
new PrintableAdapter<Student>(&andrew, &Student::getName),
new PrintableAdapter<Student>(&mark, &Student::getName),
new PrintableAdapter<Circle>(&circle1, &Circle::getInfo),
new PrintableAdapter<Circle>(&circle2, &Circle::getInfo),
new PrintableAdapter<Circle>(&circle3, &Circle::getInfo)
};
PrintList(list, 7);
return 0;
}

#include <iostream>
#include <math.h>
using namespace std;
class Student{
public:
Student(const char *_name): name(_name){}
string getName(){
return name;
}
protected:
Student(): name(""){}
string name;
};
class Circle{
public:
Circle()=delete;
Circle(double radius_): radius(radius_) {}
double Area() {return radius * radius * M_PI;}
string getInfo(){
return string("Circle with radius: ") + to_string(radius) + " and
area of " + to_string(Area());
}
private:
double radius;
};
class Printable {
public:
virtual string toString() = 0;
virtual ~Printable(){};
};
void PrintList(Printable* list[], int nrOfValues){
for (int i = 0; i < nrOfValues; ++i){
cout << i << ". " << list[i]->toString() << "\n";
}
}
template <typename AdapteeType>

Output:
0.
1.
2.
3.
4.
5.
6.

John
Michael
Andrew
Mark
Circle with radius: 3.000000 and area of 28.274334
Circle with radius: 1.500000 and area of 7.068583
Circle with radius: 10.000000 and area of 314.159265

145

Default template arguments

Classes templates (and from C++11 function templates too)


can have default arguments for type (or value) parameters

The default argument (like the default param for a function) is


specified with the = sign following the default type (or value).

For multiple arguments, all arguments after the first default


arguments must be default.

If all arguments are default, when declaring an object of such


a class and we want all arguments to be left default, we must
include empty angle brackets (<>) in the declaration
146

Example
//

16_03_Default_Template_Arguments

cout << "Array elements: [";


for_each(elements, elements + elementCount, [](NrType elem){
cout << elem << " ";
});
cout << "]\n";

#include <iostream>
using namespace std;

}
~Array() {delete [] elements;}
private:
NrType *elements = new NrType[2];
int elementCount = 0;
int currentSize = 2;
ComparatorType comp = ComparatorType();
};

template <typename NrType, typename ComparatorType=std::greater<NrType>>


class Array{
public:
Array(){}
Array(initializer_list<NrType> initList){
for_each(initList.begin(), initList.end(), [this](NrType elem){
AddElement(elem);
});
}
void AddElement(NrType elem){
if (elementCount == currentSize){
NrType *newElements = new NrType[currentSize * 2];
copy_n(elements, currentSize, newElements);
delete elements;
elements = newElements;
currentSize *= 2;
}
elements[elementCount++] = elem;
}
void Sort(){
bool sorted = false;
while (!sorted){
sorted = true;
for (int index = 0; index < elementCount - 1; ++index){
if (comp(elements[index], elements[index + 1])){
NrType aux =elements[index];
elements[index] = elements[index + 1];
elements[index + 1] = aux;
sorted = false;
}
}
}
}
void Print(){

template <typename NrType=int>


class MyComparator{
public:
bool operator()(NrType a, NrType b){
if (a>=b){
return false;
}
return true;
}
};
int main(int argc, const char * argv[]) {
Array<int> arr{10, 7, 14};
arr.AddElement(12); arr.AddElement(2); arr.AddElement(7);
arr.Sort();
arr.Print();
Array <int, MyComparator<> >arr2{10, 7, 14, 12, 2, 7};
arr2.Sort();
arr2.Print();
return 0;
}

Output:
Array elements: [2 7 7 10 12 14 ]
Array elements: [14 12 10 7 7 2 ]

147

Non-type template arguments

Templates can have arguments that are non-type:


integral and arithmetic, pointers to objects, pointers
to functions, pointers to members and lvalue
references

148

RULES:

For integral and arithmetic types, the template argument provided during instantiation
must be a converted constant expression of the template parameter's type (so certain
implicit conversion applies).

For pointers to objects, the template arguments have to designate the address of an
object with static storage duration and a linkage (either internal or external), or a
constant expression that evaluates to the appropriate null pointer or std::nullptr_t value.

For pointers to functions, the valid arguments are pointers to functions with linkage (or
constant expressions that evaluate to null pointer values).

For pointers to members, the argument has to be a pointer to member expressed as


&Class::Member or a constant expression that evaluates to null pointer or std::nullptr_t
value.

For lvalue reference parameters, the argument provided at instantiation cannot be a


temporary, an unnamed lvalue, or a named lvalue with no linkage (in other words, the
argument must have linkage).

149

Exceptions:

The only exceptions are that non-type template


parameters of reference and pointer type cannot
refer to/be the address of:
a subobject (including non-static class member,
base subobject, or array element)
a temporary object (including one created during
reference initialisation)
a string literal
the result of typeid
or the predefined variable __func__
150

Example
//

16_04_Non_Type_Template_Arguments

});
cout << "]\n";

#include <iostream>

}
~Array() {delete [] elements;}
private:
NrType *elements = new NrType[InitialCapacity];
int elementCount = 0;
ComparatorType comp = ComparatorType();
};

using namespace std;


template <typename NrType, int InitialCapacity, int & CurrentSize, typename
ComparatorType=std::greater<NrType>>
class Array{
public:
Array(){CurrentSize = InitialCapacity;}
Array(initializer_list<NrType> initList):Array(){
for_each(initList.begin(), initList.end(), [this](NrType elem){
AddElement(elem);
});
}
void AddElement(NrType elem){
if (elementCount == CurrentSize){
NrType *newElements = new NrType[CurrentSize * 2];
copy_n(elements, CurrentSize, newElements);
delete elements;
elements = newElements;
CurrentSize *= 2;
}
elements[elementCount++] = elem;
}
void Sort(){
bool sorted = false;
while (!sorted){
sorted = true;
for (int index = 0; index < elementCount - 1; ++index){
if (comp(elements[index], elements[index + 1])){
NrType aux =elements[index];
elements[index] = elements[index + 1];
elements[index + 1] = aux;
sorted = false;
}
}
}
}
void Print(){
cout << "Array elements: [";
for_each(elements, elements + elementCount, [](NrType elem){
cout << elem << " ";

template <typename NrType=int>


class MyComparator{
public:
bool operator()(NrType a, NrType b){
if (a>=b){
return false;
}
return true;
}
};
int currentArraySize = 0;
int main(int argc, const char * argv[]) {
Array<int, 2, currentArraySize> arr{10, 7, 14};
arr.AddElement(12); arr.AddElement(2); arr.AddElement(7);
arr.Sort();
arr.Print();
cout << "Current array size is: " << currentArraySize << "\n";
Array <int, 4, currentArraySize, MyComparator<> >arr2{10, 7, 14, 12, 2, 7, 8,
100, 102};
arr2.Sort();
arr2.Print();
cout << "Current array size is: " << currentArraySize << "\n";
return 0;
}

Output:
Array elements: [2 7 7 10 12 14 ]
Current array size is: 8
Array elements: [102 100 14 12 10 8 7 7 2 ]
Current array size is: 16

151

Member templates

Template declarations (class, function and variables, since C++14) can appear
inside a member specification block of any class, struct or union that arent local
classes.

If, an enclosing class is a class template, when a member template is defined


outside the class, it takes 2 sets of template parameters: the first for the
enclosing class, and the second for the member being defined

Destructors and copy constructors cannot be templates

A member function template cannot be virtual, and a member function template


in a derived class cannot override a virtual member function from the base class

A non-template member function and a template member function with the same
name can be declared. In case of conflict, the non-template member will be
used, unless an explicit template argument is specified

152

Example
//

16_05_Member_Templates

#include <iostream>

using namespace std;

capacity *= 2;
}
elements[elementCount++] = elem;

template <ostream &Output = std::cout>


void Print();
~Array() {delete [] elements;}
private:
NrType *elements = new NrType[InitialCapacity];
int elementCount = 0;
int capacity = InitialCapacity;
};

class Student{
public:
Student(string name_):name(name_){}
template <ostream &Output = std::cout>
void toString(){
Output << "My name is: " << name << "\n";
}
private:
string name = "";
};

template <typename NrType, int Capacity>


template <ostream &Output>
void Array<NrType, Capacity>::Print(){
Output << "Array elements: [";
for_each(elements, elements + elementCount, [](NrType elem){
Output << elem << " ";
});
Output << "]\n";
}

template <typename NrType, int InitialCapacity = 2>


class Array{
public:
Array(){}
Array(initializer_list<NrType> initList){
for_each(initList.begin(), initList.end(), [this](NrType
elem){
AddElement(elem);
});
}
void AddElement(NrType elem){
if (elementCount == capacity){
NrType *newElements = new NrType[capacity * 2];
copy_n(elements, elementCount, newElements);
delete [] elements;
elements = newElements;

int main(int argc, const char * argv[]) {


Student john("John");
john.toString();
john.toString<cerr>();
Array<int, 5> array{3, 1, 5, 2, 3, 5};
array.Print<cerr>();
return 0;
}

Output:
My name is: John
My name is: John
Array elements: [3 1 5 2 3 5 ]

153

Trait classes

Think of a trait as a small object whose main purpose is to carry


information used by another object or algorithm to determine
"policy" or "implementation details". - Bjarne Stroustrup

They allow us to make compile-time decisions based on types,


like out run-time decisions based on value. Sometimes the
code differs slightly based on the type of the data used

Traits rely on explicit template specialisation

stl provides the <type_traits> header that defines a series of


classes that allow us to obtain type information on compile time

154

Example
//

16_06_Trait_Classes

};

#include <iostream>

static const bool value = true;

template <typename T>


void printSomething(T t){
if (is_pointer<T>::value){
cout << "I was called a pointer type\n";
}
else {
if (is_void<T>::value){
cout << "I was called f(void)\n";
}
else if (is_int<T>::value){
cout << "I was called f(int)\n";
}
else {
cout << "I was called with a type can't handle: " <<
__PRETTY_FUNCTION__ << "\n";
}
}
}

using std::cout;
template <typename T>
struct is_pointer{
static const bool value = false;
};
template <typename T>
struct is_pointer<T*>{
static const bool value = true;
};
template <typename T>
struct is_void{
static const bool value = false;
};
template<>
struct is_void<void>{
static const bool value = true;
};

int main(int argc, const char * argv[]) {


void *a = nullptr;
int b = 2;
int *c = &b;
printSomething(a);
printSomething(b);
printSomething(c);
printSomething(3.4);
return 0;
}

template <typename T>


struct is_int{
static const bool value = false;
};
template<>
struct is_int<int>{

Output:
I
I
I
I

was
was
was
was

called
called
called
called

a pointer type
f(int)
a pointer type
with a type can't handle: void printSomething(T) [T = double]

155

Template specialisation

Specialisation is possible for: class and function templates,


member function of a class template, static data member of a
class template, member class of a class template, member
enumeration of a class template, member class template,
member function template

Explicit specialisation can only appear in the same namespace


as the primary template

Specialisation must be declared before the first use that would


cause implicit instantiation

A template specialisation that was declared but not defined, can


be used just as any incomplete type
156

Example
//

16_07_Template_Specialisation

toStringMethod = toString_;
}
~PrintableAdapter(){delete object;}
string toString(){
return (object->*toStringMethod)();
}

#include <iostream>
#include <math.h>
using namespace std;
class Student{
public:
Student(const char *_name): name(_name){}
string getName(){
return name;
}
protected:
Student(): name(""){}
string name;
};

private:
AdapteeType *object;
string (AdapteeType::*toStringMethod)();
};
template <>
class PrintableAdapter<IntNumber>: public Printable {
public:
PrintableAdapter()=delete;
PrintableAdapter(IntNumber *obj_, string(IntNumber::*toString_)()){
object = obj_;
toStringMethod = toString_;
}
~PrintableAdapter(){delete object;}
string toString(){
return string("This is an int number: ") + (object->*toStringMethod)();
}

class Circle{
public:
Circle()=delete;
Circle(double radius_): radius(radius_) {}
double Area() {return radius * radius * M_PI;}
string getInfo(){
return string("Circle with radius: ") + to_string(radius) + " and area of " + to_string(Area());
}
private:
double radius;
};

private:
IntNumber *object;
string (IntNumber::*toStringMethod)();
};

class Printable {
public:
virtual string toString() = 0;
virtual ~Printable(){};
};

int main(int argc, const char * argv[]) {


Student john("John"), michael("Michael"), andrew("Andrew"), mark("Mark");
Circle circle1(3), circle2(1.5);
IntNumber intNr(13);
Printable* list[7] = {
new PrintableAdapter<Student>(&john, &Student::getName),
new PrintableAdapter<Student>(&michael, &Student::getName),
new PrintableAdapter<Student>(&andrew, &Student::getName),
new PrintableAdapter<Student>(&mark, &Student::getName),
new PrintableAdapter<Circle>(&circle1, &Circle::getInfo),
new PrintableAdapter<Circle>(&circle2, &Circle::getInfo),
new PrintableAdapter<IntNumber>(&intNr, &IntNumber::tostr),
};
PrintList(list, 7);
return 0;
}

void PrintList(Printable* list[], int nrOfValues){


for (int i = 0; i < nrOfValues; ++i){
cout << i << ". " << list[i]->toString() << "\n";
}
}
class IntNumber{
public:
IntNumber(int value_):value(value_){}
string tostr(){
return ::to_string(value);
}
private:
int value = 0;
};
template <typename AdapteeType>
class PrintableAdapter: public Printable {
public:
PrintableAdapter()=delete;
PrintableAdapter(AdapteeType *obj_, string(AdapteeType::*toString_)()){
object = obj_;

Output:
0.
1.
2.
3.
4.
5.
6.

John
Michael
Andrew
Mark
Circle with radius: 3.000000 and area of 28.274334
Circle with radius: 1.500000 and area of 7.068583
This is an int number: 13

157

Iterators and Algorithms

The need for iterators

An iterator is an object used to traverse a container

An iterator is in fact the abstraction of a pointer to a


member of a container

The most basic for of an iterator is a simple pointer

159

Example
//
//

DoubleLinkedList.hpp
08_01_Need_For_Iterators

position = pos;
}
DoubleLinkedList::Iterator & DoubleLinkedList::Iterator::operator++(){
position = position->next;
return *this;
}
int DoubleLinkedList::Iterator::operator*(){
return position->value;
}
bool DoubleLinkedList::Iterator::operator==(Iterator it){
return (position == it.position);
}
bool DoubleLinkedList::Iterator::operator!=(Iterator it){
return (position != it.position);
}
// main.cpp
int SumOfList(DoubleLinkedList *list){
int sum = 0;
for (DoubleLinkedList::Iterator it = list->begin(); it != list->end();
++it){
sum+=*it;
}
return sum;
}
int main(int argc, const char * argv[]) {
DoubleLinkedList *list = new DoubleLinkedList();
list->addBack(10); list->addFront(20);
list->addBack(3); list->addFront(12);
list->printAll();
int sumOfAll = SumOfList(list);
cout << "Sum of all elements is: " << sumOfAll << "\n";
delete list;
return 0;
}

#include <iostream>
class DoubleLinkedList{
private:
class Node;
Node *first;
Node *last;
public:
DoubleLinkedList();
virtual ~DoubleLinkedList();
void addFront(int value);
void addBack(int value);
void printAll();
class Iterator {
friend class DoubleLinkedList;
public:
Iterator & operator ++();
bool operator ==(Iterator);
bool operator !=(Iterator);
int operator*();
private:
Iterator();
Iterator(DoubleLinkedList::Node *);
DoubleLinkedList::Node *position;
};
Iterator begin();
Iterator end();

};
// DoubleLinkedList.cpp
DoubleLinkedList::Iterator DoubleLinkedList::begin(){
return Iterator(first);
}
DoubleLinkedList::Iterator DoubleLinkedList::end(){
return Iterator(nullptr);
}
DoubleLinkedList::Iterator::Iterator(DoubleLinkedList::Node *pos) {

Output:
All values:12 20 10 3
Sum of all elements is: 45

160

The STL iterator model

STL collections provide forms of iterators for


member access

Depending of their type, certain operations can be


performed with iterators (incrementing, addition,
comparison, dereference etc)

161

Iterators - Classification
Category

Properties
copy-constructible, copy-assignable and destructible

All categories
Can be incremented
Suports equality/inequality comparisons
Input
Can be dereferenced as an rvalue
Forward Output

Can be dereferenced as an lvalue (for mutable types)

Bidirectional
default-constructible

Random
Access

Multi-pass: neither dereferencing nor incrementing affects


dereferenceability
Can be decremented
Supports arithmetic operators + and Supports inequality comparisons between iterators
Supports compound assignment operations += and -=
Supports offset dereference operator ([])

162

Valid
expressions
X b(a);
b = a;
++a;
a++;
a == b
a != b
*a
a->m
*a = t;
*a++ = t;
X a;
X();
{b = a; *a++;
*b;}
--a; a--;
*a-a + n; n + b;
a - n; a - b;
a < b; a > b;
a <= b; a >= b;
a += n;
a -= n;
a[n];

Example
//

08_02_STL_Iterators

return 0;
}

#include <iostream>
#include <list>
using namespace std;
void printList(list<float> l){
cout << "List is: ";
for (list<float>::iterator it = l.begin(); it!=l.end(); +
+it){
cout << *it << " ";
}
cout << "\n";
}
void printListReverse(list<float> l){
cout << "List in reverse: ";
for (list<float>::reverse_iterator it = l.rbegin(); it !=
l.rend(); ++it){
cout << *it << " ";
}
cout << "\n";
}
int main(int argc, const char * argv[]) {
list<float> l;
l.push_back(10); l.push_back(14);l.push_front(24);
l.push_back(3);l.push_back(7); l.push_back(9);
printList(l);
printListReverse(l);

Output:
List is: 24 10 14 3 7 9
List in reverse: 9 7 3 14 10 24

163

Function Objects

A Functions Objects, or a functor, is any object that implements


the function call operator (operator())

Unlike a straight function call, a functor can contain a state

Another advantage over simple functions is that a functor is a


type, and being a type allows it to be passed as a template
argument

They are used in containers, because some containers need to


be sorted and they need a way to compare items. Sometimes,
the items of a container are of trivial type. STL provides
functors (in <functional>) that can work with various types
164

Example
//

08_03_Function_Objects

return 0;
}

#include <iostream>
#include <list>
using namespace std;
void printList(list<float> l){
cout << "List is: ";
for (list<float>::iterator it = l.begin(); it!=l.end(); +
+it){
cout << *it << " ";
}
cout << "\n";
}
template <typename NumberType, int N>
class GreaterThan{
public:
bool operator()(NumberType nr){
return (nr > N);
}
};
int main(int argc, const char * argv[]) {
list<float> l;
l.push_back(10); l.push_back(14);l.push_front(24);
l.push_back(3);l.push_back(7); l.push_back(9);
printList(l);
l.remove_if(GreaterThan<float, 10>());
printList(l);

Output:
List is: 24 10 14 3 7 9
List is: 10 3 7 9

165

Exercise 3

Imagine a file system that contains folders and files (files can be: executables, images,
songs, movies)

Provide a method to sort all the files and folders in a folder alphabetically. Then provide
another method to sort them by size.

We should have a method .Open() that will output different text, depending on the file
type: Showing the movie <movie_name>, Displaying the image <image_name>,
Running the program <program_name> and Playing the song <song_name>). If
called on an folder, it will list all the files and folders contained.

Add a way to enumerate all the folders and files in a folder

Add a method to return the total size of a folder

Add a method to delete all the files that meet a given condition

Add a way to copy and move files


166

Day 4

Exception handling

Exception handling

Exceptions provide a way to react to exceptional circumstances by


providing control to special functions, called handlers

An exception can be thrown using the throw keyword from inside a try
block. Exception handlers are declared immediately after the try block,
using the keyword catch

The handler is called for an exception if and only if the type of its parameter
matches the type of the parameter used for the throw.

Multiple handlers can be chained, with different parameter types. Only the
one whose parameter type matches will be called

If an ellipsis is used as the parameter for a handler, that handler will be


called for any exception thrown in the try block that is not matched against
other handlers
169

Exception handling

Use throw only to signal an error (which means specifically that the function
couldnt do what it said it could)

Use catch only to specify error handling actions when you know you can
handle an error

Do not use throw to indicate a coding error in usage of a function. Use


assert or other mechanism to either send the process into a debugger or to
crash the process and collect the crash dump

Do not use throw if you discover unexpected violation of an invariant of your


component, use assert or other mechanism to terminate the program.
Throwing an exception will not cure memory corruption and may lead to
further corruption of important user data

Do not use exceptions for control flow


170

Example
//

09_01_Exception_Handling

#include <iostream>
using namespace std;
double Divide(double a, double b){
if (b == 0){
throw string("Cannot divide by 0");
}
return a/b;
}
int main(int argc, const char * argv[]) {
try {
cout << "1/2 = " << Divide(1,2) << "\n";
cout << "1/0 = " << Divide(1,0) << "\n";
} catch (std::string ex) {
cout << "EXEPTION: " << ex << "\n";
} catch (int ex){
cout << "EXEPTION Nr: " << ex << "\n";
} catch (...) {
cout << "Unknown exception!\n";
}
return 0;
}

Output:
1/2 = 0.5
1/0 = EXCEPTION: Cannot divide by 0

171

Resource acquisition idioms for exception safety

An operation on an object is know to be exception safe if that


operation leaves the object in a valid state when the operation is
terminated by throwing an exception - Bjarne Stroustrup

The Three Exception Guarantees:


No-fail Guarantee is the strongest guarantee a function can
provide. It states that the function will not throw any exceptions,
in any circumstances, nor it will allow one to propagate.
Strong Guarantee states that if a function goes out of scope
because of an exception it will not leak memory, nor it will leave
modified states (like a commit - rollback operation)
Basic Guarantee is the weakest. It states that if an exception is
thrown, it will not leak memory, but program state will be
altered, although left in an operable state
172

RAII Idiom

The RAII Idiom (Resource Acquisition Is Initialisation)


ties management of resources to the lifespan of
automatic variables.

When a function goes out of scope (either because of


an exception or returning normally) the destructors for
the scope objects are invoked

All initialisations or memory allocation should be


wrapped in an object and performed in the constructor
of this object so the destructor could handle releasing
memory or other resources and resettings states.
173

Example
//

09_02_RAII

}
private:
bool connectionInProgress;
};

#include <iostream>
using namespace std;

void SendMessage(string message){


ServerConnection conn;
conn.Connect();
if (message.length() > 0){
conn.Send(message);
} else {
throw string("MESSAGE EXEPTION: String must contain
somethig!");
}
conn.Disconnect();
}

class ServerConnection {
public:
ServerConnection(): connectionInProgress(false){}
void Connect() {
cout << "Opening server connection...\n";
connectionInProgress = true;
};
void Disconnect() {
cout << "Closing server connection...\n";
connectionInProgress = false;
}
void Send(string message){
if (connectionInProgress){
cout << "Sending message " << message << "...\n";
}
else {
cout << "Must be connected to send message. Call
Connect() first!\n";
}
}
~ServerConnection(){
if (connectionInProgress){
cout << "~ServerConnection(): Must disconnect!\n";
Disconnect();
}

int main(int argc, const char * argv[]) {


try {
SendMessage("test");
SendMessage("");
} catch (string ex) {
cout << "EXCEPTION:" << ex << "\n";
}
return 0;
}

Output:
Opening server connection...
Sending message test...
Closing server connection...
Opening server connection...
~ServerConnection(): Must disconnect!
Closing server connection...
EXCEPTION:MESSAGE EXEPTION: String must contain somethig!

174

Exceptions in constructors

If an exception is thrown in the constructor, the


object during construction doesnt get created, so
the corresponding destructor will not get called

Base destructors are guaranteed to be called

All the members in that object that were created


before the exception will be destroyed

175

Example
//

09_03_Exception_In_Constructors

Student(string name_, int year_): Person(name_){


cout << "Creating a student named " << name_ << " in
year " << year_ << "\n";
if (year < 1 || year > 4){
cout << "The student must be in year 1 to 4!
Throwing exception!\n";
throw string("The student must be in year 1 to
4!");
}
year = year_;
}
virtual ~Student(){
cout << "Destroying the student named " << name << " in
year " << year << "\n";
}
private:
Student() {}
SpecialisationList specialisationList;
int year;
};

#include <iostream>
using namespace std;
class Person {
public:
Person(string name_):name(name_){
cout << "Creating A person named " << name << "\n";
}
virtual ~Person(){
cout << "Destroying THE person named " << name << "\n";
}
protected:
Person(){}
string name;
};
class SpecialisationList{
public:
SpecialisationList(){
cout << "Creating A specialisation list\n";
}
virtual ~SpecialisationList(){
cout << "Distroying THE specialisation list\n";
}
};

int main(int argc, const char * argv[]) {


try {
Student john("John", 2);
Student jack("Jack", 0);
} catch (string ex) {
cout << "EXCEPTION: " << ex << "\n";
}
return 0;
}

class Student: public Person{


public:

Output:
Creating A person named John
Creating A specialisation list
Creating a student named John in year 2
Creating A person named Jack
Creating A specialisation list
Creating a student named Jack in year 0
The student must be in year 1 to 4! Throwing exception!
Distroying THE specialisation list
Destroying THE person named Jack

Destroying
Distroying
Destroying
EXCEPTION:

176

the
THE
THE
The

student named John in year 2


specialisation list
person named John
student must be in year 1 to 4!

Exceptions in destructors

Destructors must not throw exceptions

If a destructor handles something that might throw an


exception, it must do it in a try block and catch the
exception

A destructor may be called during an exception. If control


leaves a destructor and another exception is active, C++
calls the terminate function (which basically ends the
application and transfers the control to the OS). When
terminate is called, the program terminates immediately.
Not even local objects are destroyed
177

Example
//

09_04_Exception_In_Destructors

(lldb) bt
* thread #1: tid = 0x609015, 0x00007fff8e16f0ae
libsystem_kernel.dylib`__pthread_kill + 10, queue =
'com.apple.main-thread', stop reason = signal SIGABRT
frame #0: 0x00007fff8e16f0ae
libsystem_kernel.dylib`__pthread_kill + 10
frame #1: 0x000000010007d43d
libsystem_pthread.dylib`pthread_kill + 90
frame #2: 0x00007fffa026037b libsystem_c.dylib`abort + 129
frame #3: 0x00007fff90b12f81 libc++abi.dylib`abort_message
+ 257
frame #4: 0x00007fff90b38a47 libc+
+abi.dylib`default_terminate_handler() + 267
frame #5: 0x00007fff8f6e5173
libobjc.A.dylib`_objc_terminate() + 124
frame #6: 0x00007fff90b3619e libc+
+abi.dylib`std::__terminate(void (*)()) + 8
frame #7: 0x00007fff90b36213 libc+
+abi.dylib`std::terminate() + 51
* frame #8: 0x0000000100000ec6
09_04_Exception_In_Destructors`__clang_call_terminate + 22

#include <iostream>
using namespace std;
class TheWorstClassEver{
public:
~TheWorstClassEver(){
throw string("Exception in the destructor of class
'~TheWorstClassEver'");
}
};
int main(int argc, const char * argv[]) {
try {
TheWorstClassEver worstClassEver;
throw string("OOPS!");
} catch (string ex) {
cout << "EXCEPTION: " << ex << "\n";
}
return 0;
}

Output:
libc++abi.dylib: terminating with uncaught exception of type
std::__1::basic_string<char, std::__1::char_traits<char>,
std::__1::allocator<char> >

178

Exception safe classes

A class can help ensure its own exception safety, even if


used from less exception safe code, by preventing itself
from being partially constructed or partially destroyed

We should use smart pointers or other RAII wrappers to


manage resources and we should avoid resource
management in the destructor, unless the object is a
dedicated resource manager that manages only one item.

An exception thrown in a base class cannot be swallowed


in a derived constructor. If we want to do so, we must use
a try-catch.
179

Exception safe classes

We should store all class state in a data member


wrapped in a smart pointer, especially if the object
initialisation is permitted to fail. A constructor must
either succeed or fail, it cannot partially succeed.

We should not throw any exception from the


destructor. If it must perform an exception throwing
operation, it must do so in a try-catch block and
swallow the exception

180

STL exception safety guarantees

All the built-in types in C++ are no-fail

The STL supports basic guarantee

STL also offers strong guarantee for a few


operations that insert or remove elements. For
vector, deque, list and map all operations are either
no-fail or strong, except for N-element insert

181

STL container operations guarantee


vector

deque

list

map

clear()

nothrow (copy)

nothrow (copy)

nothrow

nothrow

erase()

nothrow (copy)

nothrow (copy)

nothrow

nothrow

1-element insert()

strong (copy)

strong (copy)

strong

strong

N-element insert()

strong (copy)

strong (copy)

strong

basic

merge()

nothrow (comp)

push_back()

strong

strong

strong

strong

strong

push_front()
pop_back()

nothrow

nothrow

nothrow

pop_front()

nothrow

nothrow

remove()

nothrow (comp)

remove_if()

nothrow (pred)

reverse()

nothrow

splice()

nothrow

swap()

nothrow

nothrow

nothrow

nothrow (copy of comp)

unique()

nothrow (comp)

182

Memory management

Object life cycle

The life cycle (or lifetime) of an object is the time between an


objects creation and its destruction

Encapsulation is important when managing an object life cycle.


The user of an object doesnt need to know what resources the
object is using and how to handle them. It is only responsible of
creating and destroying the object. C++ is designed to ensure
that objects are destroyed at the right time in the right order.

Destructors encapsulate resource release (RRID - Resource


Release Is Destruction). Owning a resource means you can
use it when needed, but you also have to release it when youre
done.
184

Example
//

10_01_Object_Lifecycle

year = 1;
}
else if (year > 4){
year = 4;
}
else {
year = year_;
}
cout << "Creating A student named " << name << " in year
" << year << "\n";
}
virtual ~Student(){
cout << "Destroying THE student named " << name << " in
year " << year << "\n";
}
private:
Student() {}
SpecialisationList specialisationList;
int year;
};

#include <iostream>
using namespace std;
class Person {
public:
Person(string name_):name(name_){
cout << "Creating A person named " << name << "\n";
}
virtual ~Person(){
cout << "Destroying THE person named " << name << "\n";
}
protected:
Person(){}
string name;
};
class SpecialisationList{
public:
SpecialisationList(){
cout << "Creating A specialisation list\n";
}
virtual ~SpecialisationList(){
cout << "Distroying THE specialisation list\n";
}
};

int main(int argc, const char * argv[]) {


Student john("John", 2);
Student *mary = new Student("Mary", 4);
{
Student jane("Jane", 5);
}
Student jack("Jack", 0);
delete mary;
return 0;
}

class Student: public Person{


public:
Student(string name_, int year_): Person(name_){
if (year < 1 || year > 4){

Output:
Creating A
Creating A
Creating A
Creating A
Creating A
Creating A
Creating A
Creating A
Creating A
Destroying
Distroying
Destroying

person named John


specialisation list
student named John in year 1
person named Mary
specialisation list
student named Mary in year 1
person named Jane
specialisation list
student named Jane in year 1
THE student named Jane in year 1
THE specialisation list
THE person named Jane

Creating A
Creating A
Creating A
Destroying
Distroying
Destroying
Destroying
Distroying
Destroying
Destroying
Distroying
Destroying

185

person named Jack


specialisation list
student named Jack in year 1
THE student named Mary in year 1
THE specialisation list
THE person named Mary
THE student named Jack in year 1
THE specialisation list
THE person named Jack
THE student named John in year 1
THE specialisation list
THE person named John

Allocation failure

Operator new is defined in three ways:


1.

void* operator new (std::size_t size) throw (std::bad_alloc);

2.

void* operator new (std::size_t size, const std::nothrow_t&


nothrow_value) throw();

3.

void* operator new (std::size_t size, void* ptr) throw();

Smart pointers do not swallow exceptions

186

Example
//

10_02_Allocation_Failure

#include <iostream>
using namespace std;
int main(int argc, const char * argv[]) {
int *elements = new(std::nothrow) int[231424324324234];
if (elements == nullptr){
cout << "ALLOCATION ERROR elements: Cannot alloc so much\n";
}
int *elements1 = nullptr;
try {
elements1 = new int[231424324324234];
} catch (std::bad_alloc ex) {
cout << "Cought bad_alloc exception: " << ex.what() << "\n";
}
if (elements1 == nullptr){
cout << "ALLOCATION ERROR elements1: Cannot alloc so much\n";
}
int *elements2 = new int[231424324324234];
if (elements2 == nullptr){
cout << "ALLOCATION ERROR elements2: Cannot alloc so much\n";
}
return 0;
}

Output:
10_02_Allocation_Failure(9798,0x100081000) malloc: ***
mach_vm_map(size=925697297297408) failed (error code=3)
*** error: can't allocate region
*** set a breakpoint in malloc_error_break to debug
ALLOCATION ERROR elements: Cannot alloc so much
10_02_Allocation_Failure(9798,0x100081000) malloc: ***
mach_vm_map(size=925697297297408) failed (error code=3)
*** error: can't allocate region
*** set a breakpoint in malloc_error_break to debug

Cought bad_alloc exception: std::bad_alloc


ALLOCATION ERROR elements1: Cannot alloc so much
10_02_Allocation_Failure(9798,0x100081000) malloc: ***
mach_vm_map(size=925697297297408) failed (error code=3)
*** error: can't allocate region
*** set a breakpoint in malloc_error_break to debug
libc++abi.dylib: terminating with uncaught exception of type
std::bad_alloc: std::bad_alloc
Program ended with exit code: 9

187

Customising memory allocation

Customising memory allocation is necessary in


some cases when a lot of small allocations are done

HEAP allocation functions from C and C++ are


optimised for large memory allocation

By customising allocation, we can alloc a big chunk


of memory one time, and then through our allocators
(or by overloading the new and delete operators)
we can assign blocks of pre-allocated memory
188

Example
//

10_03_Customising_Memory_Allocation

vector<int> *v1 = new vector<int>();


std::chrono::milliseconds ms1 =
std::chrono::duration_cast< std::chrono::milliseconds
>(std::chrono::system_clock::now().time_since_epoch());
for (int i = 0; i < 15000000; i++) {
v1->push_back(i);
}
std::chrono::milliseconds ms2 =
std::chrono::duration_cast< std::chrono::milliseconds
>(std::chrono::system_clock::now().time_since_epoch());
cout << "Finished adding elements in " << (ms2.count() ms1.count()) << "\n";
delete v1;
vector<int, MyAllocator> *v2 = new vector<int,
MyAllocator>();
ms1 = std::chrono::duration_cast<
std::chrono::milliseconds
>(std::chrono::system_clock::now().time_since_epoch());
for (int i = 0; i < 15000000; i++) {
v2->push_back(i);
}
ms2 = std::chrono::duration_cast<
std::chrono::milliseconds
>(std::chrono::system_clock::now().time_since_epoch());
cout << "Finished adding elements in " << (ms2.count() ms1.count()) << "\n";
delete v2;
return 0;
}

#include <iostream>
#include <vector>
#include <chrono>
using namespace std;
class MyAllocator{
public:
MyAllocator():currentBufferPos(0){
buffer = calloc(10000000, sizeof(int));
}
~MyAllocator(){
free(buffer);
}
typedef int value_type;
int* allocate(size_t count){
int *retPtr = (int *)buffer + currentBufferPos;
currentBufferPos += count;
return retPtr;
}
void deallocate(int *mem, size_t size){
currentBufferPos-= size * sizeof(int);
}
private:
void *buffer;
int currentBufferPos;
};
int main(int argc, const char * argv[]) {

Output:
Finished adding elements in 420
Finished adding elements in 306

189

Reference counting

Reference counting shared representation

Reference counting is useful when we need to keep


track of who uses our object, in order to know when
the object is not needed anymore

Each time our object is needed a counter is


incremented and each time our object is released a
counter is destroyed

shared_ptr implements reference counting

191

Example
//

11_01_Reference_Counting

}
operator Student*(){
return student;
}

#include <iostream>
using namespace std;

StudentPtr (const StudentPtr &studentPtr){


student = studentPtr.student;
++student->count;
}

class StudentPtr;
class Student{
public:
Student(const char *_name): name(_name), count(0){}
const string &getName(){
return name;
}
int getCount(){
return count;
}
private:
friend class StudentPtr;
Student(): name(""), count(0){}
string name;
int count;
};
class StudentPtr{
public:
StudentPtr(Student *student_) {
student = student_;
}
~StudentPtr(){
if (--student->count == 0) delete student;
}
Student &operator*(){
return *student;
}
Student *operator->(){
return student;

StudentPtr & operator=( const StudentPtr & studentPtr){


Student *oldStudent = student;
student = studentPtr.student;
++student->count;
if (--oldStudent->count == 0) delete oldStudent;
return *this;
}
private:
StudentPtr() {};
Student *student;
};
void PrintStudentName(Student *student){
cout << "The name is " << student->getName() << " and I am
referenced " << student->getCount() << " times\n";
}
int main(int argc, const char * argv[]) {
StudentPtr jane = StudentPtr(new Student("Jane")), jill =
StudentPtr(new Student("Jill"));
StudentPtr jane1 = jane;
StudentPtr jane2(jane);
PrintStudentName(jane);
PrintStudentName(jill);
return 0;
}

Output:
The name is Jane and I am referenced 2 times
The name is Jill and I am referenced 0 times

192

Reference counting strings for copy optimisation

The basic idea behind this is to allow users to think


theyre copying the strings, but the underlying
implementation doesnt actually do any copying,
unless (or until) someone tries to actually modify the
copy of the string

Our string objects contain a member where their


data is

Only when the user of our object needs to modify


the internal data, the internal data gets copied
193

Example
//

11_02_Reference_Counting_Copy_Optimisation

StudentData():name(""), year(1), count(1){}


StudentData(const char* name_, int year_): name(name_),
year(year_), count(1) {}
};
Student(): data(nullptr) {}
StudentData *data;
};

#include <iostream>
using namespace std;
class Student{
friend void PrintStudentData(Student *student);
public:
Student(const char *name_, int year_): data(new StudentData(name_,
year_)){}
~Student(){
if (--data->count == 0) delete data;
}
Student(const Student &student_):data(student_.data){
++data->count;
}
Student &operator=(const Student &student_){
StudentData *oldData = data;
data = student_.data;
++data->count;
if (--oldData->count == 0) delete oldData;
return *this;
}
const string &getName() const {
return data->name;
}

void PrintStudentData(Student *student){


cout << "My name is " << student->data->name << ", my year is " <<
student->data->year << " and I am referenced " << student->data->count << "
times\n";
}
int main(int argc, const char * argv[]) {
Student student("Jane", 2);
Student student1 = student;
Student student2(student1);
cout << "student data:\n";
PrintStudentData(&student);
cout << "Student 2's name: " << student2.getName() << "\n";
cout << "student 2 data:\n";
PrintStudentData(&student2);
student2.setName("Jill");

void setName(const char *newName){


if (data->count > 1) {
--data->count;
data = new StudentData(newName, data->year);
}
}
private:
class StudentData {
public:
string name;
int year;
int count;

cout << "student data:\n";


PrintStudentData(&student);
cout << "Student 2's name: " << student2.getName() << "\n";
cout << "student 2 data:\n";
PrintStudentData(&student2);
}

return 0;

Output:
student
My name
Student
student
My name
student
My name
Student
student

data:
is Jane, my year is 2 and I am referenced 3 times
2's name: Jane
2 data:
is Jane, my year is 2 and I am referenced 3 times
data:
is Jane, my year is 2 and I am referenced 2 times
2's name: Jill
2 data:

My name is Jill, my year is 2 and I am referenced 1 times

194

Where it fails

Circular list of reference counted objects are a


problem for reference counting

The head of the list points to the first element

The last element of the list points to the first element

If the head will become null, the first element of the


list will have its reference count decreased to 1, with
no-one from outside pointing at it, so the reference
count will never become 0
195

Smart pointers for simple garbage collection

A garbage collectors problem is finding the


garbage, not collecting it

An object is considered to be garbage if there are


no references to that object

By implementing reference counting, an object can


be destroyed when its reference count is 0

If the data structures form a reference cycle, the


trivial garbage collection will leak objects
196

Example
//

11_03_Smart_Pointers_GC

}
return *this;

#include <iostream>

}
Type &operator*(){
return *data->ptr;
}
Type *operator->(){
return data->ptr;
}
operator Type*(){
return data->ptr;
}
private:
friend void PrintStudentPtr(SmartPtr<Type> & student);
SmartPtr();
class PtrData{
public:
Type *ptr;
int count;
};
PtrData *data;
};

using namespace std;


class Student{
public:
Student(const char *_name): name(_name){}
const string &getName(){
return name;
}
private:
friend class StudentPtr;
Student(): name(""){}
string name;
};
template <typename Type>
class SmartPtr{
public:
SmartPtr(Type* ptr_){
data = new PtrData();
data->ptr = ptr_;
data->count = 1;
}
~SmartPtr() {
if (--data->count == 0){
cout << "Destroying object\n";
delete data->ptr;
delete data;
}
}
SmartPtr (const SmartPtr<Type> &smartPtr){
data = smartPtr.data;
++data->count;
}
SmartPtr<Type> & operator=( SmartPtr<Type> & smartPtr){
PtrData *oldData = data;
data= smartPtr.data;
++data->count;
if (--oldData->count == 0){
delete oldData->ptr;
delete oldData;

void PrintStudentPtr(SmartPtr<Student> &student){


cout << "The name is " << student->getName() << " and I am referenced " <<
student.data->count << " times \n";
}
int main(int argc, const char * argv[]) {
SmartPtr<Student> jane = SmartPtr<Student>(new Student("Jane")), jill =
SmartPtr<Student>(new Student("Jill"));
SmartPtr<Student> jane1 = jane;
SmartPtr<Student> jane2(jane);
PrintStudentPtr(jane);
PrintStudentPtr(jill);
return 0;

Output:
The name is Jane and I am referenced 3 times
The name is Jill and I am referenced 1 times
Destroying object
Destroying object

197

Inheritance techniques

Subtyping vs Subclassing

Subclassing allow reuse of the code inside classes,


both instance variable declarations and method
definitions. It is similar, but distinct from subtyping

Subtyping is useful in supporting reuse externally,


giving rise to a form of polymorphism. Once a data
type is determined to be a subtype of another, any
function or procedure applied to elements of the
supertype can be applied to elements of the subtype.

Subtyping establishes an is-a relationship between a


subtype and an existing abstraction
199

Example
//

12_01_Subclassing_Vs_Subtyping

Animal *justAnAnimal = new Animal();


Duck *duck = new Duck();

#include <iostream>
cout << "I am: " << justAnAnimal->what() << "\n";
cout << "I am: " << duck->what() << "\n";
duck->Fly();
duck->Talk();
return 0;

using namespace std;


class Animal{
public:
virtual string what(){
return string("Animal");
}
};

class Bird: public Animal{


public:
virtual string what(){
return string("Bird");
}
void Fly(){
cout << "I am flying like a bird!\n";
}
};
class Duck: public Bird{
public:
void Talk(){
cout << "Quack quack!\n";
}
};
int main(int argc, const char * argv[]) {

Output:
I am: Animal
I am: Bird
I am flying like a bird!
Quack quack!

200

Abstract and concrete base class

An abstract class is a class with at least one pure virtual


method. Abstract classes cannot be initialised

If we derive from an abstract class, in order to make it


concrete (therefore initialise-able), we have to implement
all the pure virtual methods

If a derived class doesnt implement all the pure virtual


methods in the base class, it is still abstract

A class becomes concrete only when all the methods in


that class have implementations
201

Example
//

12_02_Abstract_And_Concrete_Bases

pen->Write();
return 0;

#include <iostream>

using namespace std;


class Writer{
public:
virtual void Write() = 0;
void Init(const string &color_){
color = color_;
}
protected:
string color;
};
class Pen: public Writer{
public:
virtual void Write(){
cout << "Text written in " << color << "\n";
}
};
int main(int argc, const char * argv[]) {
//Writer writer; // ERROR: Variable type 'Writer' is an
abstract class
//Writer *writer = new Writer(); // ERROR: Allocating an
object of abstract class type 'Writer'
Writer *pen = new Pen();
pen->Init("Red");

Output:
Text written in Red

202

Inheritance scoping issues

Function overloading sometimes raises some issues when deriving a base


class

Overloads are resolved one scope at a time

Classes have different scopes, if there is an inheritance relationship


between them, the scope of the derived class is nested within the scope of
its direct and indirect base classes

Lookup happens by searching up the inheritance hierarchy until the given


name is found. Names defined in a derived class hide uses of that name
inside a base.

Under multiple inheritance, this same lookup happens simultaneously


among all the direct base classes. If a name is found through more than
one base class, then use of that name is ambiguous.
203

Example
//

12_03_Inheritance_Scoping_Issues

delete magicNr;
return 0;

#include <iostream>

using namespace std;


void printValue(int val){
cout << "Global: The value is " << val << "\n";
}
template <typename NrType>
class Number {
public:
void printValue(NrType val){
cout << "Class Number: The value is " << val << "\n";
}
};
template <typename NrType>
class MagicNumber: public Number<NrType> {
public:
void print(){
printValue(val);
}
MagicNumber(NrType val_): val(val_){}
NrType val;
};
int main(int argc, const char * argv[]) {
MagicNumber<int> *magicNr = new MagicNumber<int>(2);
magicNr->print();

Output:
Global: The value is 2

204

Multiple inheritance

In C++ a class can inherit from multiple classes, no


matter if they are abstract or concrete

Beside the Diamond problem, another problem with


inheritance is the physical layout of objects in
memory

205

Example
//

12_04_Multiple_Inheritance

private:
string manufacturer;
Phone() : manufacturer("") {};
};

#include <iostream>
using namespace std;
class ConnectedDevice {
public:
ConnectedDevice() : macAddress("") {}
string const &getMacAddress(){
return macAddress;
}
void setMacAddress(const string & macAddr){
macAddress = macAddr;
}
private:
string macAddress;
};

class Smartphone: public Phone, public Computer {


public:
Smartphone (const char *manufacturer, const char *os) :
Phone(manufacturer), Computer(os) {};
void printDetails(){
//cout << "I am made by " << getManufacturer() << " and I run "
<< getOS() << ". My MAC address is: " << getMacAddress() << "\n"; //
ERROR: Non-static member 'getMacAddress' found in multiple base-class
}
};
int main(int argc, const char * argv[]) {
Smartphone galaxyPhone("Samsung", "Android");
Smartphone iPhone("Apple", "iOS");

class Computer: public ConnectedDevice {


public:
Computer (const char *os) : os(os){};
string const & getOS(){
return os;
}
private:
string os;
Computer() : os("") {};
};

//iPhone.setMacAddress("01:02:03:04:05:06"); // ERROR: Nonstatic member 'getMacAddress' found in multiple base-class subobjects


of type 'ConnectedDevice':
//galaxyPhone.setMacAddress("00:00:00:00:00:00"); /*ERROR: Nonstatic member 'setMacAddress' found in multiple base-class subobjects
of type 'ConnectedDevice':
/*
class Smartphone -> class Phone -> class ConnectedDevice
class Smartphone -> class Computer -> class ConnectedDevice
*/
galaxyPhone.printDetails();
iPhone.printDetails();
return 0;
}

class Phone: public ConnectedDevice {


public:
Phone(const char *manufacturer) : manufacturer(manufacturer){};
string const & getManufacturer (){
return manufacturer;
}

Output:
?

206

Virtual base classes

Virtual base classes offer a way to save space and


avoid ambiguities when dealing with multiple
inheritance

When a base class is specified as a virtual base, it


can act as an indirect base more than once without
duplication of its data members. That is, a single
copy of its data members is shared by all base
classes that use it as a virtual base

207

Example
//

12_05_Virtual_Base_Class

private:
string manufacturer;
Phone() : manufacturer("") {};
};

#include <iostream>
using namespace std;
class ConnectedDevice {
public:
ConnectedDevice() : macAddress("") {}
string const &getMacAddress(){
return macAddress;
}
void setMacAddress(const string & macAddr){
macAddress = macAddr;
}
private:
string macAddress;
};

class Smartphone: public Phone, public Computer {


public:
Smartphone (const char *manufacturer, const char *os) :
Phone(manufacturer), Computer(os) {};
void printDetails(){
cout << "I am made by " << getManufacturer() << " and I run "
<< getOS() << ". My MAC address is: " << getMacAddress() << "\n";
}
};
int main(int argc, const char * argv[]) {
Smartphone galaxyPhone("Samsung", "Android");
Smartphone iPhone("Apple", "iOS");

class Computer: public virtual ConnectedDevice {


public:
Computer (const char *os) : os(os){};
string const & getOS(){
return os;
}
private:
string os;
Computer() : os("") {};
};

iPhone.setMacAddress("01:02:03:04:05:06");
galaxyPhone.setMacAddress("00:00:00:00:00:00");

class Phone: public virtual ConnectedDevice {


public:
Phone(const char *manufacturer) : manufacturer(manufacturer){};
string const & getManufacturer (){
return manufacturer;
}

Output:
I am made by Samsung and I run Android. My MAC address is:
00:00:00:00:00:00
I am made by Apple and I run iOS. My MAC address is:
01:02:03:04:05:06

208

galaxyPhone.printDetails();
iPhone.printDetails();
return 0;

Interface classes

An interface class is a class that contains only a virtual


destructor and pure virtual functions

We use them to separate the interface of a class from its


implementation

Dependency Inversion Principle (DIP) states that implementation


classes should not depend on each other. Instead they should
depend on a common abstraction represented using an
interface class. This is a good way to reduce coupling in OOP.

Every interface class should have a virtual destructor. This


ensures that when an interface class is deleted (through
polymorphism), the correct destructor is called.
209

Example
//

12_06_Interface_Classes
int main(int argc, const char * argv[]) {
Shape *s = new Square(4);
s->Draw();
s->Resize(2); s->Draw();
s->Resize(0.1); s->Draw();
delete s;
return 0;
}

#include <iostream>
using namespace std;
class Shape{
public:
virtual double Area() = 0;
virtual void Rotate(double) = 0;
virtual void Resize(double) = 0;
virtual void Draw() = 0;
virtual ~Shape() {}
};
class Square: public Shape{
public:
Square(): sideLength(0){}
Square(double sideLength_): sideLength(sideLength_) {}
virtual double Area() {return sideLength * sideLength;}
virtual void Rotate(double) { /* My square does not
support rotate */}
virtual void Resize(double factor) { sideLength *=
factor;}
virtual void Draw(){
cout << "Drawing a square shape with the size length of "
<< sideLength << " and an area of " << Area() << "\n";
}
virtual ~Square() {}
private: double sideLength;
};

Output:
Drawing a square shape with the size length of 4 and an area of 16
Drawing a square shape with the size length of 8 and an area of 64
Drawing a square shape with the size length of 0.8 and an area of 0.64

210

Mix-in classes

Mix-ins are classes formed of primitive classes that are


not related. The primitive classes can be used as building
blocks

We can add more primitive classes and construct objects


in the same way without affecting the existing ones

We achieve this through templates and inheritance

This primitive classes are connected together by


providing them as template parameters
211

Example
//

12_07_Mixin_Classes

class Smartphone: public PhoneType {


public:
Smartphone (const char *manufacturer, const char *os) :
PhoneType(manufacturer, os) {};
void printDetails(){
cout << "I am made by " << this->getManufacturer() << "
and I run " << this->getOS() << "\n";
}
};

#include <iostream>
using namespace std;
class Computer {
public:
Computer (const char *os) : os(os){};
string const & getOS(){
return os;
}
protected:
std::string os;
Computer() : os("") {};
};

int main(int argc, const char * argv[]) {


Smartphone <Phone <Computer> > galaxyPhone("Samsung",
"Android");
Smartphone <Phone <Computer> > iPhone("Apple", "iOS");
galaxyPhone.printDetails();
iPhone.printDetails();
return 0;

template <typename ComputerType>


class Phone : public ComputerType{
public:
Phone(const char *manufacturer, const char *os) :
ComputerType(os), manufacturer(manufacturer){};
string const & getManufacturer (){
return manufacturer;
}
protected:
string manufacturer;
Phone() : manufacturer("") {};
};

template <typename PhoneType>

Output:
I am made by Samsung and I run Android
I am made by Apple and I run iOS

212

RTTI (Runtime Type Information)

RTTI (Runtime Type Information or Runtime Type


Identification) exposes information about objects
data type at runtime

Useful to perform safe casts and manipulate type


information at runtime

The typeid operator uses RTTI to provide type


information (it returns a type_info class)

213

Example
//

12_08_RTTI

Circle(double radius_): radius(radius_) {}


virtual double Area() {return radius * radius * M_PI;}
virtual void Rotate(double) { /* My circle cannot rotate */}
virtual void Resize(double factor) { radius *= factor;}
virtual void Draw(){
cout << "Drawing a circle shape with the radius of " <<
radius << " and an area of " << Area() << "\n";
}
virtual ~Circle() {}
private:
double radius;
};

#include <iostream>
#include <math.h>
using namespace std;
class Shape{
public:
virtual double Area() = 0;
virtual void Rotate(double) = 0;
virtual void Resize(double) = 0;
virtual void Draw() = 0;
virtual ~Shape() {}
};

void drawAShape(Shape *shape){


if (typeid(*shape) == typeid(Circle)){
cout << "I have to draw a circle:\n";
} else {
cout << "I have to draw another shape:\n";
}
shape->Draw();
}

class Square: public Shape{


public:
Square(): sideLength(0){}
Square(double sideLength_): sideLength(sideLength_) {}
virtual double Area() {return sideLength * sideLength;}
virtual void Rotate(double) { /* My square does not support
rotate */}
virtual void Resize(double factor) { sideLength *= factor;}
virtual void Draw(){
cout << "Drawing a square shape with the size length of " <<
sideLength << " and an area of " << Area() << "\n";
}
virtual ~Square() {}
private:
double sideLength;
};

int main(int argc, const char * argv[]) {


Shape *square = new Square(2);
Shape *circle = new Circle(4);
drawAShape(square);drawAShape(circle);

class Circle: public Shape{


public:
Circle(): radius(0){}

Output:
I have to
Drawing a
I have to
Drawing a

draw another shape:


square shape with the size length of 2 and an area of 4
draw a circle:
circle shape with the radius of 4 and an area of 50.2655

214

delete square; delete circle;


return 0;

Class Adapter Pattern

Unlike the Object Adapter pattern, the Class


Adapter pattern uses inheritance to make existing
classes work with other ones

This pattern uses multiple polymorphic interfaces,


implementing or inheriting both the interface that is
expected and the interface that needs adapting

Usually, the expected interface is created as in


interface class
215

Diagram

216

Example
//

12_09_Class_Adapter_Pattern

StudentAdapter(Student *_student): Student(*_student){}


virtual string toString(){
return string(getName());
}
private:
StudentAdapter() {}
};

#include <iostream>
using namespace std;
class Student{
public:
Student(const char *_name): name(_name){}
const string &getName(){
return name;
}
protected:
Student(): name(""){}
string name;
};

int main(int argc, const char * argv[]) {


Student john("John"), michael("Michael"),
andrew("Andrew"), mark("Mark");
Printable* list[4] = {new StudentAdapter(&john), new
StudentAdapter(&michael), new StudentAdapter(&andrew), new
StudentAdapter(&mark)};
PrintList(list, 4);
return 0;
}

class Printable {
public:
virtual string toString(){
return string("");
};
};
void PrintList(Printable* list[], int nrOfValues){
for (int i = 0; i < nrOfValues; ++i){
cout << i << ". " << list[i]->toString() << "\n";
}
}
class StudentAdapter: public Printable, public Student{
public:

Output:
0.
1.
2.
3.

John
Michael
Andrew
Mark

217

Exercise 4

Element is a class

Row is a mix-in (over Element). Basically it contains a number of elements

Matrix is a mix-in (over Row). Basically it contains a number or rows

Implement an allocator. The allocator will have a MAX_SIZE buffer

Create a Matrix that uses an the custom allocator and another one that doesnt.

Throw an exception if an allocation is required and no more space is available in


the buffer

Make sure the buffer in the matrix is not copied unless its modified

Adapt the Element, the Row and the Matrix to the same printable interface
218

Day 5

C++11 language
enhancements

The auto keyword and decltype

In C++03 we need to specify the type of a variable at


declaration

C++11 takes advantage of the fact that a variable is also


initialised and allows us to declare an object without
specifying its type, deducing it from the initialisation

It is very useful when the type is verbose or when its


automatically generated (from templates)

The new operator decltype captures the type of an object


or an expression
221

Example
//

14_01_Auto_Decltype

for (VectorIterator vecIt = intNumbers->begin(); vecIt


intNumbers->end(); ++vecIt) {
if ((*vecIt) > max){
max = (*vecIt);
}
}

#include <iostream>
#include <vector>
using namespace std;
auto Sum(vector<int> & numbers) -> int{
int sum = 0;
auto it = numbers.begin();
while(it != numbers.end()){
sum += (*it++);
}
return sum;
}

cout << "Max element in vector is: " << max << "\n";
delete intNumbers;
return 0;
}

int main(int argc, const char * argv[]) {


auto intNumbers = new vector<int>();
intNumbers->push_back(1); intNumbers->push_back(23);
intNumbers->push_back(2); intNumbers->push_back(34);
intNumbers->push_back(12); intNumbers->push_back(12);
intNumbers->push_back(45); intNumbers->push_back(4);
auto sumOfAll = Sum(*intNumbers);
cout << "The sum of all elements is: " << sumOfAll <<
"\n";
typedef decltype(intNumbers->begin()) VectorIterator;
auto max = (*intNumbers)[0];

Output:
The sum of all elements is: 133
Max element in vector is: 45

222

!=

Uniform initialisation syntax

C++11 adds an uniform brace notation

It can also be used to initialise containers

In C++11, in-class initialisation of data members in


possible

223

Example
//

14_02_Uniform_Initialization

auto Sum(vector<int> & numbers) -> int{


int sum = 0;
auto it = numbers.begin();
while(it != numbers.end()){
sum += (*it++);
}
return sum;
}

#include <iostream>
#include <vector>
#include <map>
using namespace std;
template <typename T>
class Numbers{
public:
Numbers(initializer_list<T> initList): numbers(initList)
{}
auto getCount() -> int{
return count;
}

int main(int argc, const char * argv[]) {


Numbers<int> intNumbers = {1, 23, 2, 34, 12, 12, 45, 4};
map<string, int> ages = {
{"Jane", 15},
{"Mark", 4},
{"James", 12}
};

auto Sum() -> T{


T sum = 0;
auto it = numbers.begin();
while(it != numbers.end()){
sum += (*it++);
}
return sum;
}

auto sumOfAll = intNumbers.Sum();


cout << "The sum of all elements is: " << sumOfAll <<
"\n";
return 0;
}

private:
int count = 0;
vector<T> numbers;
};

Output:
The sum of all elements is: 133

224

delete and default

A defaulted function (=default after the declaration)


instructs the compiler to generate the default
implementation for that function (used mainly for
constructors, destructors etc)

A deleted function (=delete) does the opposite. It


tells the compiler to delete that function. It is very
useful to prevent an object from being copied or
constructed in some unwanted way.

225

Example
//

14_03_Delete_And_Default

};
int main(int argc, const char * argv[]) {
//Student s; // ERROR: Call to deleted constructor of
'Student'
Student jane("Jane");
Student jenny(jane);
Student jena = jane;
//jena.Heigth(2); // ERROR: Call to deleted member
function 'Heigth'
cout << "Jena's name is: " << jena.Name() << "\n";
return 0;
}

#include <iostream>
using namespace std;
class Person{
public:
Person()=delete;
Person(const string &name_): name(name_){}
Person(const Person &) = default;
Person &operator=(const Person&) = delete;
void Heigth(int h) {heigth = h;}
int Heigth() {return heigth;}
const string &Name(){
return name;
}
private:
string name="";
int heigth = 0;
};
class Student: public Person{
public:
Student()=delete;
Student(const string &name_): Person(name_){}
Student(const Student &) = default;
Student& operator=(const Student&)=default;
void Heigth(int h)=delete;
int Heigth()=delete;

Output:
Jena's name is: Jane

226

nullptr

nullptr replaces the NULL macro and 0 which were


used as null pointer substitutes

It is strongly typed and is applicable to all pointers,


and pointers to members

227

Example
//

14_04_nullptr

#include <iostream>
using namespace std;
void f(int x){
cout << "f(int x) was called\n";
}
void f(char *){
cout << "f(char *) was called\n";
}
int main(int argc, const char * argv[]) {
//f(NULL); // ERROR: Call to 'f' is ambiguous
f(nullptr);
f(0);
return 0;
}

Output:
f(char *) was called
f(int x) was called

228

Delegating constructors

In C++03, we couldnt call an objects constructor


from another one of the same class

In C++11, we can call a constructor from another


constructor of the same class

229

Example
//

14_05_Delegating_Constructors

#include <iostream>
#include <string>
using namespace std;
class Student{
public:
Student()=delete;
Student(string name_, int year_): name(name_), year(year_)
{}
Student(string name_): Student(name_, 1){}
Student(int year_):Student("", year_){};
string &&toString(){
return string("Name: ") + name + "; year: " +
to_string(year);
}
private:
string name = "";
int year = 1;
};
int main(int argc, const char * argv[]) {
Student jane("Jane", 2);
Student july("July");
cout << jane.toString() << "\n";
cout << july.toString() << "\n";
return 0;
}

Output:
Name: Jane; year: 2
Name: July; year: 1

230

Lambdas

A Lambda expression lets us define a function


locally, at the place of the call

It has the form: [capture](parameters)->return_type{body}

The [] construct inside a function calls argument list


indicates the beginning of a lambda expression

Lambdas can catch variables (or objects) and use


them by reference (using an &) or by value
231

Example
//

14_06_Lambdas

#include <iostream>
#include <vector>
using namespace std;
int main(int argc, const char * argv[]) {
vector<int> numbers = {2, 12, -3, 4, 4, 2, -5};
int max = numbers[0];
for_each(numbers.begin(), numbers.end(), [&max](int nr){
if (nr > max){
max = nr;
}
});
auto printOutput = [max](){
cout << "The max is " << max << "\n";
};
printOutput();
[](){cout << "Another message\n";}();
int x = 2;
auto y = [&r = x, x = x + 1]()->int { // C++14
r += 2;
return x + 2;
}();
[](int a, int b){
cout << "(" << a << ", " << b << ")\n";
}(x, y);
return 0;
}

Output:
The max is 12
Another message
(4, 5)

232

rvalue references

An rvalue is a temporary value that does not persist


beyond the expression that uses it

rvalues references (&&) can bind to rvalues.

The primary reason for adding rvalue references is


move semantics

Using an rvalue reference, for example, we can


overload a function to behave differently when
called with an rvalue.
233

Example
//

14_07_RValue_References
int &&x = 2 + 3;
x+=2;

#include <iostream>
cout << "X = " << x << "\n";
using namespace std;

void f(int& x)
{
cout << "lvalue reference overload f(" << x << ")\n";
}
void f(const int& x)
{
cout << "lvalue reference to const overload f(" << x << ")
\n";
}
void f(int&& x)
{
cout << "rvalue reference overload f(" << x << ")\n";
}
int main(int argc, const char * argv[]) {
int i = 1;
const int ci = 2;
f(i); // calls f(int&)
f(ci); // calls f(const int&)
f(3); // calls f(int&&)
// would call f(const int&) if f(int&&) overload wasn't
provided
return 0;

Output:
lvalue reference overload f(1)
lvalue reference to const overload f(2)
rvalue reference overload f(3)
X = 7

234

Move constructor

Move constructor is like a copy constructor that


takes an rvalue as an argument.

Instead of copying, moving might be preferred as


its faster just exchanging pointers to data than
actually copying the data

move semantics is heavily used in the new STL


classes

235

Example
//

14_08_Move_Constructor

free(p.name);
}

#include <iostream>
Person & operator=(Person &&p){
cout << "Person & operator=(Person &&p) called!\n";
name = strdup(p.name);
free(p.name);
return *this;
}

using namespace std;


class Person{
public:
Person()=delete;
Person(const char *name_){
cout << "PersonPerson(const char *name_) constructor
called!\n";
if (name != nullptr){
name = strdup(name_);
}
else {
name = strdup("");
}
}
Person(const Person &p){
cout << "Person(const Person &p) constructor called!\n";
name = strdup(p.name);
}
Person & operator=(Person &p){
cout << "Person & operator=(const Person &p) called!\n";
name = strdup(p.name);
return *this;
}
Person(Person&& p){
cout << "Person(Person&& p) constructor called!\n";
name = strdup(p.name);

private:
char *name;
};
Person f(Person p){
return p;
}
int main(int argc, const char * argv[]) {
Person john("John");
Person johnny(john);
johnny = john;
Person jane(f(Person("Jane")));
jane = f(Person("Jill"));
return 0;
}

Output:
PersonPerson(const char *name_) constructor called!
Person(const Person &p) constructor called!
Person & operator=(const Person &p) called!
PersonPerson(const char *name_) constructor called!
Person(Person&& p) constructor called!
PersonPerson(const char *name_) constructor called!
Person(Person&& p) constructor called!
Person & operator=(Person &&p) called!

236

Additions to the standard library

STL includes new container classes: unordered_set,


unordered_map, unordered_multiset and unordered_multimap

New libs for regular expressions, tuples, function object wrapper

New Threading Library (with promises, futures, the async()


function for launching concurrent tasks, the thread_local storage
type etc.)

New Algorithms: all_of(), any_of() and none_of()

C++11 still lacks a garbage collector, a very useful XML API (or
a JSON API), sockets, GUI, reflection
237

Example
//

14_09_Additions_To_STL

});
bool noNrPositive = none_of(numbers.begin(),
numbers.end(), [](int nr)->bool{
return (nr >= 0);
});

#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;

auto bToStr = [](bool b) -> string{


if (b) return "YES";
return "NO";
};

class Person{
public:
Person(string name_):name(name_) {}
const string & Name(){
return name;
}
private:
string name = "";
};

cout << "Are any numbers positive? " <<


bToStr(anyNrPositive) << "\n";
cout << "Are all numbers positive? " <<
bToStr(allNrPositive) << "\n";
cout << "Are no numbers positive? " <<
bToStr(noNrPositive) << "\n";

int main(int argc, const char * argv[]) {


auto john = unique_ptr<Person>(new Person("John"));
auto jack = shared_ptr<Person>(new Person("Jack"));
cout << "Name: " << john->Name() << "\n";
cout << "Name: " << jack->Name() << "\n";
vector<int> numbers = {1,2, 3, 4, -1};
bool anyNrPositive = any_of(numbers.begin(),
numbers.end(), [](int nr)->bool{
return (nr >= 0);
});
bool allNrPositive = all_of(numbers.begin(),
numbers.end(), [](int nr)->bool{
return (nr >= 0);

return 0;
}

Output:
Name: John
Name: Jack
Are any numbers positive? YES
Are all numbers positive? NO
Are no numbers positive? NO

238

The final keyword

Specifies that a virtual function cannot be overridden in a derived


class or that a class cannot be inherited from

When used in a virtual function declaration or definition, final


ensures that the function is virtual and specifies that it may not
be overridden by derived classes.

When used in a class definition, final specifies that this class may
not appear in the base-specifier-list of another class definition (in
other words, cannot be derived from)

It is an identifier with a special meaning when used in a member


function declaration or class head. In other contexts it is not
reserved and may be used to name objects and functions
239

Example
//

14_10_Keyword_final

int main(int argc, const char * argv[]) {


return 0;
}

#include <iostream>
#include <math.h>
using namespace std;
class Point final{
public:
int x, y;
void print(){
std::cout << "(" << x << "," << y << ")\n";
}
};
class Circle{
public:
Circle()=delete;
Circle(double radius_): radius(radius_) {}
virtual double Area() final {return radius * radius *
M_PI;}
string getInfo(){
return string("Circle with radius: ") + to_string(radius)
+ " and area of " + to_string(Area());
}
private:
double radius;
int final = 2;
};

Output:

240

Multithreading
techniques

Creating multithreaded applications with std::thread

Threads allow multiple pieces of code to run asynchronously


and simultaneously

The class std::thread creates a single thread of execution

No to std::thread objects may represent the same thread of


execution

A std::thread object my be in a state that does not represent any


thread (after default construction, move from, detach, or join)

A thread of execution my not be associated with any thread


object (after detach)
242

Essential threading concepts

Multi-threaded applications introduce concepts like synchronisation,


critical sections, shared resources, promises, futures

Usually, shared resources are CREW (Concurrent Read, Exclusive


Write)

In special cases, resources might be EREW (Exclusive Read,


Exclusive Write)

A thread must use a lock, to let other threads know that a resource is
in use, and must release it after finishing the operation

A deadlock occurs when one or more threads wait for a resource


and that resource never gets released
243

Example
//

15_01_Threads

outputLock.unlock();
}

#include <iostream>
#include <thread>
#include <future>

int main(int argc, const char * argv[]) {


thread t1 (printANumberAndSleep, 3, 2);
thread t2 (printANumberAndSleep, 12, 3);
t2.join();
cout << "Joined t2\n";
t1.join();
cout << "Joined t1\n";
promise<int> prom;
future<int> futureNumber = prom.get_future();
thread promiseToPrintSomething(printInTheFuture,
ref(futureNumber));
thread t3 (safePrintANrAndSleep, 3, 2);
thread t4 (safePrintANrAndSleep, 12, 3);
t4.join();
cout << "Joined t3\n";
t3.join();
cout << "Joined t4\n";
prom.set_value(12);

using namespace std;


mutex outputLock;
void printANumberAndSleep(int nr, int timeout){
cout << "This is a number: ";
cout << nr << "\n";
sleep(timeout);
}
void safePrintANrAndSleep(int nr, int timeout){
outputLock.lock();
cout << "This is a number: ";
cout << nr << "\n";
outputLock.unlock();
sleep(timeout);
}

promiseToPrintSomething.join();
return 0;

void printInTheFuture(future<int> &f){


outputLock.lock();
cout << "Waiting to print something in the future...\n";
outputLock.unlock();
int x = f.get();
outputLock.lock();
cout << "The number in the future is: " << x << "\n";

Output:
TThhiiss iiss aa nnuummbbeerr:: 31
2
Joined t2
Joined t1
This is a number: 3
Waiting to print something in the future...
This is a number: 12
Joined t3
Joined t4
The number in the future is: 12

244

Producer Consumer

There can be one or more producers and one or more consumers

Every producer and consumer use a shared resource, usually a


queue: the producer(s) produce data and put it in the queue as they
produce it, and the consumers take data from the queue and
consume it

The buffer is limited in size

Only one entity (producer or consumer) can read or write in the shared
buffer at any given time (the buffer is EREW)

It is used when constant data is fed from one or multiple sources and
must be processed (like HTTP servers, file servers, database readers
etc)
245

Example

Implement a writer function that inserts the integer received as


a parameter in the shared queue.

In the main() function, in an infinite loop, instantiate 3 threads


that run the aforementioned function. The parameter given to
the function will start from 1, and the main loop will keep
incrementing it for subsequent calls. After the three treads are
joined, sleep for 1 second before repeating.

In the main() function, before the infinite loop, instantiate 5


threads. They will each run the same function that: if the queue
is empty, sleeps for one second before checking again, if its
not empty, reads an element and outputs it to the system
console.
246

Readers Writers

It is a classic concurrency problem involving threads


(or processes) that access a shared resource at the
same time, either for reading or for writing

The main constraint is that no other thread (or process)


can access the resource while its being written, while
multiple readers can read the shared resource at the
same time

It can be used for a counter accessed by multiple


threads (let as many threads read it, but block it for
writing)
247

Example

Take the previous example (the producer consumer)


and make the buffer count (or the boolean
is_buffer_empty variable) to be able to be read by
as many threads needed, but to be written
exclusively.

248

Dining philosophers problem

Five silent philosophers sit at a round table with bowls of spaghetti.


Forks are placed between each pair of adjacent philosophers

Each philosopher must alternately think and eat. However, a


philosopher can only eat spaghetti when he has both left and right
forks. Each fork can be held by only one philosopher and so a
philosopher can use the fork only if it is not being used by another
philosopher. After he finishes eating, he needs to put down both
forks so they become available to others. A philosopher can take
the fork on his right or the one on his left as they become available,
but cannot start eating before getting both of them

The problem was designed to illustrate the challenges of avoiding


deadlock, a system state in which no progress is possible
249

Example

Create 5 philosophers

Each philosopher will do a random amount of


eating and a random amount of thinking (not more
than 10 seconds each)

Keep track, for each philosopher, of the amount of


reading and thinking done

250

Cigarette smokers problem

Assume a cigarette requires three ingredients to make and smoke:


tobacco, paper, and matches.

There are three smokers around a table, each of whom has an infinite
supply of one of the three ingredients one smoker has an infinite
supply of tobacco, another has paper, and the third has matches.

There is also a non-smoking agent who enables the smokers to make


their cigarettes by arbitrarily (non-deterministically) selecting two of
the supplies to place on the table. The smoker who has the third
supply should remove the two items from the table, using them (along
with their own supply) to make a cigarette, which they smoke for a
while. Once the smoker has finished their cigarette, the agent places
two new random items on the table. This process continues forever

251

Example

Create 3 resources: tobacco, paper and matches

Create 3 smokers, each one having only one of the


resources newly created. Each smoker smokes for a
random amount of time. Keep track of the amount of
time smoked by each smoker

Create the non smoking agent. It will arbitrarily


make available two resources as soon as no smoker
is busy smoking or making a cigarette.
252

Sleeping barber problem

The barber has one barber chair and a waiting room with a number
of chairs in it. When the barber finishes cutting a customer's hair, he
dismisses the customer and then goes to the waiting room to see if
there are other customers waiting. If there are, he brings one of them
back to the chair and cuts his hair. If there are no other customers
waiting, he returns to his chair and sleeps in it.

Each customer, when he arrives, looks to see what the barber is


doing. If the barber is sleeping, then the customer wakes him up
and sits in the chair. If the barber is cutting hair, then the customer
goes to the waiting room. If there is a free chair in the waiting room,
the customer sits in it and waits his turn. If there is no free chair, then
the customer leaves.

This can be generalised by adding barbers and barber chairs


253

Example

Create a barber. And his chair. Each haircut will last a


random amount of time, no more than 5 seconds.

Create a waiting room with 4 chairs.

Create an infinite loop that creates a client and then sleeps


a random amount of time, but no more than 10 seconds,
before creating another one.

Keep track of how long the barber sleeps and cuts hair.
See what happens with the barber if the main thread can
sleep no more than 5 seconds, or at least 10 seconds etc.
What if each haircut lasts for a fixed amount?
254

Bibliography

Bibliography

http://en.cppreference.com/w/

http://www.cplusplus.com/

http://www.stroustrup.com/except.pdf

http://www.stroustrup.com/3rd_safe.pdf

http://www.cs.princeton.edu/courses/archive/fall98/cs441/
mainus/node12.html

http://www.parashift.com/c++-faq/

https://sourcemaking.com/design_patterns

https://en.wikibooks.org/wiki/More_C%2B%2B_Idioms

https://www.wikipedia.org/

http://www.stroustrup.com/bs_faq.html
256

Books

Thinking in C++ (Vol I & II), Bruce Eckel

Design Patterns: Elements of Reusable ObjectOriented Software, Erich Gamma, Richard Helm,
Ralph Johnson and John Glissades (The Gang of
Four)

Modern C++ Design: Generic Programming and


Design Patterns Applied, Andrei Alexandrescu

Inside the C++ Object Model, Stanley B. Lippman


257

You might also like