Advanced C++
Cosmin Stan
cosminclaudiustan@gmail.com
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