SlideShare uma empresa Scribd logo
1 de 143
Resource wrappers
Ilio Catallo - info@iliocatallo.it
Outline
• The importance of std::vector
• Representa2on
• Ini2aliza2on
• Accessing elements
• Destruc2on
Outline
• Copying
• Assignment
• Rule of three
• Bibliography
The importance of
std::vector
The ubiquity of std::vector
The most useful container in the C++ standard library is
std::vector
Why std::vector is so useful
std::vector is built from low-level memory management
facili5es, such as pointers and arrays
Why std::vector is so useful
std::vector's primary role is to help us avoid the complexi4es of
those facili4es
Why std::vector is so useful
That is, std::vector is designed to insulate us from some of the
unpleasant aspects of real memory
Building std::vector
In the remainder, we will write a simplified std::vector
implementa0on
Building std::vector
Namely, we will write a non-resizable version of std::vector for
the limited case of int elements
The vector_int class
We will refer to this type as vector_int
Why bother re-inven.ng the wheel when we already have
std::vector?
But why?
The reason is that re-implemen0ng std::vector allows us to
prac0ce many basic language facili0es at once
• Pointers & arrays
• Classes & operator overloading
But why?
Secondly, it allows stressing that whenever we design a class, we
must consider ini#aliza#on, copying and destruc#on1
1
For simplicity, in the remainder we are going to deliberately ignore move seman8cs, i.e., we are going to discuss
object lifecycle as of C++03
But why?
Finally, as computer scien2sts we need to know how to design and
implement abstrac2ons such as std::vector
Objec&ves
Ideally, we would like to achieve the following:
void f() {
vector_int v(7); // varying number of elements
v[0] = 7; // writing elements
std::cout << v.size(); // it knows its size
std::cout << v[1]; // values are default initialized
vector_int w = v; // copy construction
vector_int u; u = v; // assignment
// automatic memory management
// (no delete[] required)
}
Natural behavior
vector_int provides us opera,ons that seem natural from the
viewpoint of a user, rather than from the viewpoint of hardware
Natural behavior
We want to get to the point where we can program using types
that provide exactly the proper4es we want based on logical needs
Natural behavior
To get there, we have to overcome a number of fundamental
constraints related to access to the bare machine, such as:
• An object in memory is of fixed size
• An object in memory is in one specific place
Representa)on
Designing vector_int
We start our incremental design of vector_int by considering a
very simple use:
vector_int grades(4);
grades[0] = 28;
grades[1] = 27;
grades[2] = 30:
grades[3] = 25;
Designing vector_int
This creates a vector_int with four elements and give those
elements the values 28, 27, 30, 25
vector_int grades(4);
grades[0] = 28;
grades[1] = 27;
grades[2] = 30:
grades[3] = 25;
The size of a vector_int
The number of elements of a vector_int is called its size
vector_int grades(4);
grades[0] = 28;
grades[1] = 27;
grades[2] = 30:
grades[3] = 25;
The size of a vector_int
Therefore, the size of grades is four
vector_int grades(4);
grades[0] = 28;
grades[1] = 27;
grades[2] = 30:
grades[3] = 25;
The size of a vector_int
Moreover, the number of elements of a vector_int are indexed
from 0 to size - 1
vector_int grades(4);
grades[0] = 28;
grades[1] = 27;
grades[2] = 30:
grades[3] = 25;
Represen'ng grades
Graphically, we can represent grades like:
grades[0] grades[1] grades[2] grades[3]
┌───────────┬───────────┬──────────┬───────────┐
│ │ │ │ │
└───────────┴───────────┴──────────┴───────────┘
▲
┌─────────┘
│
│
┌─────┬─────┐
grades: │ 4 │ │
└─────┴─────┘
How do we implement that drawing in code?
vector_int is a class
Unsurprisingly, we have to define a class and call it vector_int
class vector_int {
...
};
Data members
Obviously, we cannot design vector_int to have a fixed number
of elements
class vector_int {
public:
...
private:
int elem0, elem1, elem2, elem3;
};
Data members
Conversely, we need a data member that points to the sequence of
elements
class vector_int {
public:
...
private:
int* elem;
};
Data members
That is, we need a pointer member to the sequence of elements
class vector_int {
public:
...
private:
int* elem;
};
Data members
Furthermore, we need a data member to hold the size of the
sequence
class vector_int {
public:
...
private:
std::size_t sz;
int* elem;
};
grades representa)on
Therefore, a refined grades representa0on is as follows:
elem[0] elem[1] elem[2] elem[3]
┌───────────┬───────────┬──────────┬───────────┐
│ │ │ │ │ int[4]
└───────────┴───────────┴──────────┴───────────┘
▲
┌─────────┘
│
sz elem
┌─────┬─────┐
grades: │ 4 │ │ vector_int
└─────┴─────┘
Ini$aliza$on
Ini$aliza$on
At construc*on *me, we would like to allocate sufficient space for
the elements on the heap
// v allocates 4 ints on the heap
vector_int v(4);
Ini$aliza$on
Elements will be later accessed through the data member v.elem
class vector_int {
public:
...
private:
std::size_t sz;
int* elem;
};
Alloca&ng space
To this end, we use new in the constructor to allocate space for the
elements, and we let elem point to the address returned by new
class vector_int {
public:
vector_int(std::size_t sz): elem(...) {}
...
}
Alloca&ng space
To this end, we use new in the constructor to allocate space for the
elements, and we let elem point to the address returned by new
class vector_int {
public:
vector_int(std::size_t sz): elem(new int[sz]) {}
...
}
Storing the size
Moreover, since there is no way to recover the size of the dynamic
array from elem, we store it in sz
class vector_int {
public:
vector_int(std::size_t sz): sz(sz),
elem(new int[sz]) {}
...
};
Storing the size
We want users of vector_int to be able to get the number of
elements
size() access func)on
Hence, we provide an access func2on size() that returns the
number of elements
class vector_int {
public:
vector_int(std::size_t sz): sz(sz),
elem(new int[sz]) {}
std::size_t size() const { return sz; }
private:
std::size_t sz;
int* elem;
};
Sensible ini)aliza)on
In addi'on, we would like to ini'alize the newly-allocated elements
to a sensible value
vector_int v(3);
std::cout << v[0]; // output: 0 (as opposed to some random values)
Sensible ini)aliza)on
Again, we can do that at construc2on 2me
vector_int v(3);
std::cout << v[0]; // output: 0 (as opposed to some random values)
Sensible ini)aliza)on
class vector_int {
public:
vector_int(std::size_t sz): sz(sz),
elem(new int[sz]) {
for (std::size_t i = 0; i < sz; ++i)
elem[i] = 0;
}
std::size_t size() const { return sz; }
private:
std::size_t sz;
int* elem;
};
Default constructor
Moreover, in the case of containers, we have a natural default valid
state, i.e., the empty container
Default constructor
Hence, we can introduce a default constructor
class vector_int {
public:
vector_int(): sz(0), elem(nullptr) {}
vector_int(std::size_t sz): sz(sz), elem(new int[sz]) {...}
private:
std::size_t sz;
int* elem;
};
Accessing elements
vector_int as a pointer
Note that – up to this point – vector_int values are just pointers
that remember the size of the pointed array
vector_int v(4);
std::cout << v.size(); // output: 4
Accessing elements
However, for a vector_int to be usable, we need a way to read
and write elements
vector_int v(4);
v[0] = 1; // writing elements
std::cout << v[0]; // reading elements
Accessing elements
That is, we would like to add the possibility of access elements
through our usual subscript nota-on
vector_int v(4);
v[0] = 1; // writing elements
std::cout << v[0]; // reading elements
Operator func-on
The way to get that is to define a member operator func,on
operator[]
vector_int v(4);
v[0] = 1; // writing elements
std::cout << v[0]; // reading elements
Defining operator[]
How can we define the operator[] overload?
class vector_int {
public:
...
??? operator[](std::size_t i) { ??? }
private:
std::size_t sz;
int* elem;
};
Defining operator[]
Intui&vely, one would say
class vector_int {
public:
...
int operator[](std::size_t i) { return elem[i]; }
private:
std::size_t sz;
int* elem;
};
Defining operator[]
Apparently, we can now manipulate elements in a vector_int
vector_int v(10);
int x = v[2];
std::cout << x; // output: 0
Naive implementa,on
This looks good and simple, but unfortunately it is too simple
int operator[](std::size_t i) { return elem[i]; }
Naive implementa,on
What happens when we write the following?
vector_int v(10);
v[3] = 7;
Naive implementa,on
We get a compile-)me error :-(
vector_int v(10);
v[3] = 7; // compile-time error!
Returning a value
Our implementa-on of operator[] returns a temporary of type
int. Hence, we cannot assign any addi-onal value to it
vector_int v(10);
v[3] = 7;
Returning a value
That is, le+ng the subscript operator return a value enables
reading but not wri6ng elements
int operator[](std::size_t i) { return elem[i]; }
Returning a reference
Returning a reference, rather than a value, from the subscript
operator solves this problem
int& operator[](std::size_t i) { return elem[i]; }
Returning a reference
class vector_int {
public:
...
int& operator[](std::size_t i) { return elem[i]; }
private:
std::size_t sz;
int* elem;
};
Constant vector_ints
Our subscript operator has s/ll a problem, namely, it cannot be
invoked for constant vector_ints
void p(vector_int const& v) {
int x = v[0];
}
Constant vector_ints
This is because operator[] has not been marked as const
void p(vector_int const& v) {
int x = v[0];
}
Constant vector_ints
However, we cannot simply mark operator[] as const, as it will
prevent any further modifica9on to its elements
void p(vector_int const& v) {
int x = v[0];
}
Const-overload
To solve this, we provide an addi$onal overload of operator[]
specifically meant for accessing const vectors
class vector_int {
public:
...
int& operator[](std::size_t i) { return elem[i]; }
int operator[](std::size_t i) const { return elem[i]; }
private:
std::size_t sz;
int* elem;
};
Const-overload
We can now access elements of constant vector_ints without
preven4ng the possibility of modifying non-const vector_ints
class vector_int {
public:
...
int& operator[](std::size_t i) { return elem[i]; }
int operator[](std::size_t i) const { return elem[i]; }
private:
std::size_t sz;
int* elem;
};
Destructor
Resource acquisi,on
No#ce that in the constructor we allocate memory for the elements
using new
class vector_int {
public:
vector_int(std::size_t sz): sz(sz),
elem(new int[sz]) {}
...
};
Memory leak
However, when leaving f(), the heap-allocated elements pointed
to by v.elem are not released
void f() {
vector_int v(10);
int e = v[0];
};
Memory leak
We are therefore causing a memory leak
void f() {
vector_int v(10);
int e = v[0];
};
Fixing the leak
To solve this, we must make sure that this memory is freed using
delete[]
void f() {
vector_int v(10);
int e = v[0];
// we should call delete[] before
// returning from f()
};
The clean_up() member
We could define a clean_up() member func0on
class vector_int {
public:
vector_int(std::size_t sz): sz(sz), elem(new int[sz]) {...}
void clean_up() { delete[] elem; }
...
private:
std::size_t sz;
int* elem;
};
The clean_up() member
We can then call clean_up() as follows
void f() {
vector_int v(10);
int e = v[0];
v.clean_up();
};
Releasing the memory
Although that would work, one of the most common problems with
the heap is that it is extremely easy to forget to call delete
void f() {
vector_int v(10);
int e = v[0];
v.clean_up();
};
Releasing the memory
The equivalent problem would arise for clean_up()
void f() {
vector_int v(10);
int e = v[0];
v.clean_up();
};
Destructors
Fortunately, C++ provides facili6es that allow us to do be:er than
that
Destructors
In par'cular, in C++ each class is provided with a special func,on
that makes sure that an object is properly cleaned up before it is
destroyed
Destructors
Such a special func.on is called the destructor
Destructors
The destructor is a public member func-ons with no input
parameters and no return type
class vector_int {
public:
~vector_int() {...}
...
};
Destructors
The destructor has the same name as the class, but with a !lde (~)
in front of it
class vector_int {
public:
~vector_int() {...}
...
};
Implemen'ng ~vector_int()
class vector_int {
public:
vector_int(std::size_t sz): sz(sz), elem(new int[sz]) {...}
~vector_int() { ??? }
...
private:
std::size_t sz;
int* elem;
};
Implemen'ng ~vector_int()
class vector_int {
public:
vector_int(std::size_t sz): sz(sz), elem(new int[sz]) {...}
~vector_int() { delete[] elem; }
...
private:
std::size_t sz;
int* elem;
};
Automa'c memory dealloca'on
Thanks to the presence of the destructor, we do not need to
explicitly release memory
void f() {
vector_int v(10);
int e = v[0];
// Hurray! the dynamic arrays pointed to
// by v.elem is automatically deleted
};
Automa'c memory dealloca'on
The tremendous advantage is that a vector cannot forget to call its
destructor to deallocate the memory used for its elements
Synthesized destructors
If we do not explicitly provide a destructor, the compiler will
generate a synthesized destructor
Synthesized destructors
Such a synthesized destructor invokes the destructors for the
elements (if they have destructors)
vector_int synth. destructor
Since sz and elem do not provide a destructor, the synthesized
destructor would simply reduce to an empty func8on
class vector_int {
public:
// synthesized destructor
~vector_int() {}
...
private:
std::size_t sz;
int* elem;
};
vector_int synth. destructor
This is why we end up with a memory leak if we do not explicitly
specify a destructor for vector_int
class vector_int {
public:
// synthesized destructor
~vector_int() {}
...
private:
std::size_t sz;
int* elem;
};
Copying
Copying two vector_ints
Let us try to copy one vector_int into another
void h() {
vector_int v(3);
v[0] = 7;
v[1] = 3;
v[2] = 8;
vector_int w = v; // what happens?
}
Copying two vector_ints
Ideally we would like w to become a copy of v
void h() {
vector_int v(3);
v[0] = 7;
v[1] = 3;
v[2] = 8;
vector_int w = v; // what happens?
}
Copy seman+cs
That means:
• w.size() == v.size()
• w[i] == v[i] for all i's in [0:v.size())
• &w[i] != &v[i] for all i's in [0:v.size())
Copy seman+cs
┌───────────┬───────────┬───────────┐
│ 7 │ 3 │ 8 │ int[3]
└───────────┴───────────┴───────────┘
▲
┌─────────┘
│
sz elem
┌─────┬─────┐
v: │ 3 │ │ vector_int
└─────┴─────┘
sz elem
┌─────┬─────┐
w: │ 3 │ │ vector_int
└─────┴─────┘
│
└──────────┐
│
▼
┌───────────┬───────────┬──────────┐
│ 7 │ 3 │ 8 │ int[3]
└───────────┴───────────┴──────────┘
Copy seman+cs
Furthermore, we want all memory to be released once returning
from h()
void h() {
vector_int v(3);
v[0] = 7;
v[1] = 3;
v[2] = 8;
vector_int w = v;
// here, v.elem and w.elem get released
}
Wrong copy behavior
Unfortunately, this is not what happens with our vector_int
implementa4on
Copying is ini*alizing
First, note that w is ini#alized from v
void h() {
vector_int v(3);
v[0] = 7;
v[1] = 3;
v[2] = 8;
vector_int w = v;
}
Copying is ini*alizing
We know that ini#aliza#on is done by a constructor
void h() {
vector_int v(3);
v[0] = 7;
v[1] = 3;
v[2] = 8;
vector_int w = v;
}
Copying is ini*alizing
Hence, we have to conclude that vector_int provides a
constructor that accepts vector_ints as its only input argument
vector_int(vector_int const& v) {...}
Copying is ini*alizing
This is made even more evident if we use an equivalent nota4on
for the last line in h()
void h() {
vector_int v(3);
v[0] = 7;
v[1] = 3;
v[2] = 8;
vector_int w(v); // the same as: vector_int w = v;
}
Copying is ini*alizing
However, we never wrote such a constructor
Copy constructor
It turns out that every class in C++ is provided with a special
constructor called copy constructor
vector_int(vector_int const& v) {...}
Copy constructor
A copy constructor is defined to take as its argument a constant
reference to the object from which to copy
vector_int(vector_int const& v) {...}
vector_int's constructors
class vector_int {
public:
vector_int(): sz(0), elem(nullptr) {}
vector_int(std::size_t sz): sz(sz), elem(new int[sz]) {...}
vector_int(vector_int const& v) { ... }
private:
std::size_t sz;
int* elem;
};
Const-reference
Note that the v is passed by reference
vector_int(vector_int const& v) {...}
Const-reference
Why don't we pass v by value?
vector_int(vector_int const& v) {...}
Const-reference
Passing by value would require v to be copied...
vector_int(vector_int const& v) {...}
Const-reference
...which would in turn cause the invoca2on of v's copy
constructor...
vector_int(vector_int const& v) {...}
Const-reference
...thus leading to an infinite loop!
vector_int(vector_int const& v) {...}
Synthesized copy constructor
If we do not provide an explicit copy constructor, the compiler will
generate a synthesized one for us
Synthesized copy constructor
Synthesized copy constructors simply perform memberwise copy
class vector_int {
public:
// synthesized copy constructor
vector_int(vector_int const& v): sz(v.sz),
elem(v.elem) {}
...
private:
std::size_t sz;
int* elem;
};
Copying pointers
However, memberwise copying in the presence of pointer
members (such as elem) usually causes problems
// synthesized copy constructor
vector_int(vector_int const& v): sz(v.sz),
elem(v.elem) {}
Copying pointers
Specifically, w ends up sharing v's elements
void h() {
vector_int v(3);
v[0] = 7;
v[1] = 3;
v[2] = 8;
vector_int w = v;
}
Sharing elements
sz elem
┌─────┬─────┐
w: │ 3 │ │ vector_int
└─────┴─────┘
│
└──────────┐
│
▼
┌───────────┬───────────┬───────────┐
│ 7 │ 3 │ 8 │ int[3]
└───────────┴───────────┴───────────┘
▲
┌─────────┘
│
sz elem
┌─────┬─────┐
v: │ 3 │ │ vector_int
└─────┴─────┘
Sharing elements
What is the output?
vector_int v(3);
v[0] = 7;
v[1] = 3;
v[2] = 8;
vector_int w = v;
w[0] = 10;
std::cout << v[0];
Sharing elements
What is the output?
vector_int v(3);
v[0] = 7;
v[1] = 3;
v[2] = 8;
vector_int w = v;
w[0] = 10;
std::cout << v[0]; // output: 10
Double dele)on
Moreover, when we return from h() the destructors for v and w
are implicitly called
void h() {
vector_int v(3);
v[0] = 7;
v[1] = 3;
v[2] = 8;
vector_int w = v;
}
Double dele)on
Due to their hidden connec-on, both v and w will try to release the
same memory area
A working copy constructor
Hence, we need to explicitly provide a copy constructor that will
1. set the number of elements (i.e., the size)
2. allocate memory for its elements
3. copying the elements from the source vector_int
A working copy constructor
class vector_int {
public:
vector_int(vector_int const& v): sz(v.sz),
elem(new int[v.sz]) {
std::copy(v.elem, v.elem + sz, elem);
}
...
private:
std::size_t sz;
int* elem;
};
A working copy constructor
void h() {
vector_int v(3);
v[0] = 7;
v[1] = 3;
v[2] = 8;
vector_int w = v;
}
A working copy constructor
┌───────────┬───────────┬───────────┐
│ 7 │ 3 │ 8 │ int[3]
└───────────┴───────────┴───────────┘
▲
┌─────────┘
│
sz elem
┌─────┬─────┐
v: │ 3 │ │ vector_int
└─────┴─────┘
sz elem
┌─────┬─────┐
w: │ 3 │ │ vector_int
└─────┴─────┘
│
└──────────┐
│
▼
┌───────────┬───────────┬──────────┐
│ 7 │ 3 │ 8 │ int[3]
└───────────┴───────────┴──────────┘
No more double dele+on
Given that the two vector_ints are now independent, the two
destructors can do the right thing
void h() {
vector_int v(3);
v[0] = 7;
v[1] = 3;
v[2] = 8;
vector_int w = v;
}
Assignment
Assignment
Even though we now correctly handle copy construc4on,
vector_ints can s4ll be copied by assignment
void g() {
vector_int v(3);
v[0] = 7;
v[1] = 3;
v[2] = 8;
vector_int w(4); w = v;
}
Assignment
What happens when we execute the following?
void g() {
vector_int v(3);
v[0] = 7;
v[1] = 3;
v[2] = 8;
vector_int w(4); w = v; // what happens?
}
Memory leak and double dele/on
Unfortunately, we once again end up with a memory leak and a
double dele-on
void g() {
vector_int v(3);
v[0] = 7;
v[1] = 3;
v[2] = 8;
vector_int w(4); w = v;
}
Synthesized assignment operator
As a ma&er of fact, if we do not provide any overload for the
assignment operator, the compiler will synthesize one for us
Synthesized assignment operator
Synth. assignment operators perform memberwise assignment
class vector_int {
public:
// synthesized assignment operator
vector_int& operator=(vector_int const& v) {
sz = v.sz;
elem = v.elem;
return *this;
}
...
private:
std::size_t sz;
int* elem;
};
Synthesized assignment operator
┌───────────┬───────────┬───────────┬───────────┐
│ 0 │ 0 │ 0 │ 0 │ int[4]
└───────────┴───────────┴───────────┴───────────┘
▲
┌───────┘
│
│
sz elem
┌─────┬─────┐
w: │ 4 │ │ vector_int
└─────┴─────┘
┌───────────┬───────────┬───────────┐
│ 7 │ 3 │ 8 │ int[3]
└───────────┴───────────┴───────────┘
▲
┌─────────┘
│
sz elem
┌─────┬─────┐
v: │ 3 │ │ vector_int
└─────┴─────┘
Synthesized assignment operator
┌───────────┬───────────┬───────────┬───────────┐
│ 0 │ 0 │ 0 │ 0 │ int[4]
└───────────┴───────────┴───────────┴───────────┘
sz elem
┌─────┬─────┐
w: │ 3 │ │ vector_int
└─────┴─────┘
│
└──────────┐
│
▼
┌───────────┬───────────┬───────────┐
│ 7 │ 3 │ 8 │ int[3]
└───────────┴───────────┴───────────┘
▲
┌─────────┘
│
sz elem
┌─────┬─────┐
v: │ 3 │ │ vector_int
└─────┴─────┘
Synth. assignment for vector_int
Since we did not provide an explicit overload for operator=, the
synthesized assignment is used
vector_int& operator=(vector_int const& v) {...}
Working assignment operator
The remedy for the assignment is fundamentally the same as for
the copy constructor
vector_int& operator=(vector_int const& v) {...}
Working assignment operator
class vector_int {
public:
vector_int& operator=(vector_int const& v) {
int* p = new int[v.sz];
std::copy(v.elem, v.elem + v.sz, p);
delete[] elem;
elem = p;
sz = v.sz;
return *this;
}
...
private:
std::size_t sz;
int* elem;
};
Rule of three
Destructors are fundamental
Destructors are conceptually simple but are the founda'on for
many of the most effec5ve C++ programming techniques
Destructors are fundamental
The usage of the constructor / destructor pair for correctly
managing heap-allocated memory is the archetypical example
Resource wrappers need destructors
More generally, a class needs a destructor if it acquires resources
Resource
A resource is something we get from somewhere and that we must
give back once we have finished using it
• Memory
• File descriptor
• Socket
• ...
The big three
A class that needs a destructor almost always needs a copy
construc-on and assignment
The big three
The reason is that if an object has acquired a resource the default
meaning of copy is almost certainly wrong
The rule of three
This is summarised in the so-called rule of three2
If you need to explicitly declare either the destructor, copy constructor
or assignment operator yourself, you probably need to explicitly declare
all three of them
2
Once again, we are limi/ng ourselves to C++03, in C++11 one would obey either the rule of five or the rule of zero
Bibliography
Bibliography
• B. Stroustrup, The C++ Programming Language (4th
ed.)
• B, Stroustrup, Programming: Principles and Prac@ce
Using C++ (2nd
ed.)
• A. Stepanov, Notes on programming
• StackOverflow FAQ, What is the rule of three?

Mais conteúdo relacionado

Mais procurados

2.overview of c++ ________lecture2
2.overview of c++  ________lecture22.overview of c++  ________lecture2
2.overview of c++ ________lecture2
Warui Maina
 
C Prog - Pointers
C Prog - PointersC Prog - Pointers
C Prog - Pointers
vinay arora
 

Mais procurados (20)

Chap 5 c++
Chap 5 c++Chap 5 c++
Chap 5 c++
 
Chap 6 c++
Chap 6 c++Chap 6 c++
Chap 6 c++
 
Ch7 structures
Ch7 structuresCh7 structures
Ch7 structures
 
Chap 6 c++
Chap 6 c++Chap 6 c++
Chap 6 c++
 
02. Data Types and variables
02. Data Types and variables02. Data Types and variables
02. Data Types and variables
 
C++ Language
C++ LanguageC++ Language
C++ Language
 
Pointers in C
Pointers in CPointers in C
Pointers in C
 
02. Primitive Data Types and Variables
02. Primitive Data Types and Variables02. Primitive Data Types and Variables
02. Primitive Data Types and Variables
 
Chap 5 c++
Chap 5 c++Chap 5 c++
Chap 5 c++
 
2.overview of c++ ________lecture2
2.overview of c++  ________lecture22.overview of c++  ________lecture2
2.overview of c++ ________lecture2
 
Arrays
ArraysArrays
Arrays
 
C Prog - Pointers
C Prog - PointersC Prog - Pointers
C Prog - Pointers
 
13. Java text processing
13.  Java text processing13.  Java text processing
13. Java text processing
 
Computer Programming- Lecture 7
Computer Programming- Lecture 7Computer Programming- Lecture 7
Computer Programming- Lecture 7
 
46630497 fun-pointer-1
46630497 fun-pointer-146630497 fun-pointer-1
46630497 fun-pointer-1
 
Array&amp;string
Array&amp;stringArray&amp;string
Array&amp;string
 
19. Data Structures and Algorithm Complexity
19. Data Structures and Algorithm Complexity19. Data Structures and Algorithm Complexity
19. Data Structures and Algorithm Complexity
 
Pointers+(2)
Pointers+(2)Pointers+(2)
Pointers+(2)
 
Java Foundations: Strings and Text Processing
Java Foundations: Strings and Text ProcessingJava Foundations: Strings and Text Processing
Java Foundations: Strings and Text Processing
 
Chap 4 c++
Chap 4 c++Chap 4 c++
Chap 4 c++
 

Destaque

Destaque (9)

Ays Summer Meet the Stars
Ays Summer Meet the StarsAys Summer Meet the Stars
Ays Summer Meet the Stars
 
Partnering with communities in Colorado
Partnering with communities in ColoradoPartnering with communities in Colorado
Partnering with communities in Colorado
 
Business health check business infoload 2016
Business health check   business infoload 2016Business health check   business infoload 2016
Business health check business infoload 2016
 
JSpiders - Wrapper classes
JSpiders - Wrapper classesJSpiders - Wrapper classes
JSpiders - Wrapper classes
 
Are you keen to know about upcoming trends in Live Event Industry
Are you keen to know about upcoming trends in Live Event IndustryAre you keen to know about upcoming trends in Live Event Industry
Are you keen to know about upcoming trends in Live Event Industry
 
Operator overloading in C++
Operator overloading in C++Operator overloading in C++
Operator overloading in C++
 
Regular types in C++
Regular types in C++Regular types in C++
Regular types in C++
 
C++ 11 range-based for loop
C++ 11   range-based for loopC++ 11   range-based for loop
C++ 11 range-based for loop
 
C++ Standard Template Library
C++ Standard Template LibraryC++ Standard Template Library
C++ Standard Template Library
 

Semelhante a Resource wrappers in C++

cpp programing language exercise Vector.ppt
cpp programing language exercise Vector.pptcpp programing language exercise Vector.ppt
cpp programing language exercise Vector.ppt
ssuser8ac8e7
 
I am trying to fill out a program where the method definitions will b.docx
I am trying  to fill out a program where the method definitions will b.docxI am trying  to fill out a program where the method definitions will b.docx
I am trying to fill out a program where the method definitions will b.docx
Phil4IDBrownh
 
Vectors Intro.ppt
Vectors Intro.pptVectors Intro.ppt
Vectors Intro.ppt
puneet680917
 
The deadline of submission is February 15th. Acceptable file format f.pdf
The deadline of submission is February 15th. Acceptable file format f.pdfThe deadline of submission is February 15th. Acceptable file format f.pdf
The deadline of submission is February 15th. Acceptable file format f.pdf
fashionfootwear1
 

Semelhante a Resource wrappers in C++ (20)

cpp programing language exercise Vector.ppt
cpp programing language exercise Vector.pptcpp programing language exercise Vector.ppt
cpp programing language exercise Vector.ppt
 
Oops lab manual2
Oops lab manual2Oops lab manual2
Oops lab manual2
 
Vector class in C++
Vector class in C++Vector class in C++
Vector class in C++
 
Op ps
Op psOp ps
Op ps
 
Vector3
Vector3Vector3
Vector3
 
What's New in C++ 11/14?
What's New in C++ 11/14?What's New in C++ 11/14?
What's New in C++ 11/14?
 
Intro To C++ - Class #18: Vectors & Arrays
Intro To C++ - Class #18: Vectors & ArraysIntro To C++ - Class #18: Vectors & Arrays
Intro To C++ - Class #18: Vectors & Arrays
 
I am trying to fill out a program where the method definitions will b.docx
I am trying  to fill out a program where the method definitions will b.docxI am trying  to fill out a program where the method definitions will b.docx
I am trying to fill out a program where the method definitions will b.docx
 
Vectors Intro.ppt
Vectors Intro.pptVectors Intro.ppt
Vectors Intro.ppt
 
Chapter1.pptx
Chapter1.pptxChapter1.pptx
Chapter1.pptx
 
c_tutorial_2.ppt
c_tutorial_2.pptc_tutorial_2.ppt
c_tutorial_2.ppt
 
C Programming - Refresher - Part III
C Programming - Refresher - Part IIIC Programming - Refresher - Part III
C Programming - Refresher - Part III
 
C++ references
C++ referencesC++ references
C++ references
 
CLASSES, STRUCTURE,UNION in C++
CLASSES, STRUCTURE,UNION in C++CLASSES, STRUCTURE,UNION in C++
CLASSES, STRUCTURE,UNION in C++
 
02.adt
02.adt02.adt
02.adt
 
Arrays, Structures And Enums
Arrays, Structures And EnumsArrays, Structures And Enums
Arrays, Structures And Enums
 
Lecture 2 java.pdf
Lecture 2 java.pdfLecture 2 java.pdf
Lecture 2 java.pdf
 
OOP program questions with answers
OOP program questions with answersOOP program questions with answers
OOP program questions with answers
 
The deadline of submission is February 15th. Acceptable file format f.pdf
The deadline of submission is February 15th. Acceptable file format f.pdfThe deadline of submission is February 15th. Acceptable file format f.pdf
The deadline of submission is February 15th. Acceptable file format f.pdf
 
Class and object C++.pptx
Class and object C++.pptxClass and object C++.pptx
Class and object C++.pptx
 

Mais de Ilio Catallo

Community Detection
Community DetectionCommunity Detection
Community Detection
Ilio Catallo
 
WWW12 - The CUbRIK Project
WWW12 - The CUbRIK ProjectWWW12 - The CUbRIK Project
WWW12 - The CUbRIK Project
Ilio Catallo
 

Mais de Ilio Catallo (17)

Memory management in C++
Memory management in C++Memory management in C++
Memory management in C++
 
Spring MVC - Wiring the different layers
Spring MVC -  Wiring the different layersSpring MVC -  Wiring the different layers
Spring MVC - Wiring the different layers
 
Java and Java platforms
Java and Java platformsJava and Java platforms
Java and Java platforms
 
Spring MVC - Web Forms
Spring MVC  - Web FormsSpring MVC  - Web Forms
Spring MVC - Web Forms
 
Spring MVC - The Basics
Spring MVC -  The BasicsSpring MVC -  The Basics
Spring MVC - The Basics
 
Web application architecture
Web application architectureWeb application architecture
Web application architecture
 
Introduction To Spring
Introduction To SpringIntroduction To Spring
Introduction To Spring
 
Gestione della memoria in C++
Gestione della memoria in C++Gestione della memoria in C++
Gestione della memoria in C++
 
Array in C++
Array in C++Array in C++
Array in C++
 
Puntatori e Riferimenti
Puntatori e RiferimentiPuntatori e Riferimenti
Puntatori e Riferimenti
 
Java Persistence API
Java Persistence APIJava Persistence API
Java Persistence API
 
JSP Standard Tag Library
JSP Standard Tag LibraryJSP Standard Tag Library
JSP Standard Tag Library
 
Internationalization in Jakarta Struts 1.3
Internationalization in Jakarta Struts 1.3Internationalization in Jakarta Struts 1.3
Internationalization in Jakarta Struts 1.3
 
Validation in Jakarta Struts 1.3
Validation in Jakarta Struts 1.3Validation in Jakarta Struts 1.3
Validation in Jakarta Struts 1.3
 
Introduction to Struts 1.3
Introduction to Struts 1.3Introduction to Struts 1.3
Introduction to Struts 1.3
 
Community Detection
Community DetectionCommunity Detection
Community Detection
 
WWW12 - The CUbRIK Project
WWW12 - The CUbRIK ProjectWWW12 - The CUbRIK Project
WWW12 - The CUbRIK Project
 

Último

IAC 2024 - IA Fast Track to Search Focused AI Solutions
IAC 2024 - IA Fast Track to Search Focused AI SolutionsIAC 2024 - IA Fast Track to Search Focused AI Solutions
IAC 2024 - IA Fast Track to Search Focused AI Solutions
Enterprise Knowledge
 

Último (20)

Boost PC performance: How more available memory can improve productivity
Boost PC performance: How more available memory can improve productivityBoost PC performance: How more available memory can improve productivity
Boost PC performance: How more available memory can improve productivity
 
The Role of Taxonomy and Ontology in Semantic Layers - Heather Hedden.pdf
The Role of Taxonomy and Ontology in Semantic Layers - Heather Hedden.pdfThe Role of Taxonomy and Ontology in Semantic Layers - Heather Hedden.pdf
The Role of Taxonomy and Ontology in Semantic Layers - Heather Hedden.pdf
 
How to convert PDF to text with Nanonets
How to convert PDF to text with NanonetsHow to convert PDF to text with Nanonets
How to convert PDF to text with Nanonets
 
08448380779 Call Girls In Friends Colony Women Seeking Men
08448380779 Call Girls In Friends Colony Women Seeking Men08448380779 Call Girls In Friends Colony Women Seeking Men
08448380779 Call Girls In Friends Colony Women Seeking Men
 
Boost Fertility New Invention Ups Success Rates.pdf
Boost Fertility New Invention Ups Success Rates.pdfBoost Fertility New Invention Ups Success Rates.pdf
Boost Fertility New Invention Ups Success Rates.pdf
 
Workshop - Best of Both Worlds_ Combine KG and Vector search for enhanced R...
Workshop - Best of Both Worlds_ Combine  KG and Vector search for  enhanced R...Workshop - Best of Both Worlds_ Combine  KG and Vector search for  enhanced R...
Workshop - Best of Both Worlds_ Combine KG and Vector search for enhanced R...
 
Evaluating the top large language models.pdf
Evaluating the top large language models.pdfEvaluating the top large language models.pdf
Evaluating the top large language models.pdf
 
Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...
Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...
Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...
 
presentation ICT roal in 21st century education
presentation ICT roal in 21st century educationpresentation ICT roal in 21st century education
presentation ICT roal in 21st century education
 
Raspberry Pi 5: Challenges and Solutions in Bringing up an OpenGL/Vulkan Driv...
Raspberry Pi 5: Challenges and Solutions in Bringing up an OpenGL/Vulkan Driv...Raspberry Pi 5: Challenges and Solutions in Bringing up an OpenGL/Vulkan Driv...
Raspberry Pi 5: Challenges and Solutions in Bringing up an OpenGL/Vulkan Driv...
 
Understanding Discord NSFW Servers A Guide for Responsible Users.pdf
Understanding Discord NSFW Servers A Guide for Responsible Users.pdfUnderstanding Discord NSFW Servers A Guide for Responsible Users.pdf
Understanding Discord NSFW Servers A Guide for Responsible Users.pdf
 
The 7 Things I Know About Cyber Security After 25 Years | April 2024
The 7 Things I Know About Cyber Security After 25 Years | April 2024The 7 Things I Know About Cyber Security After 25 Years | April 2024
The 7 Things I Know About Cyber Security After 25 Years | April 2024
 
Partners Life - Insurer Innovation Award 2024
Partners Life - Insurer Innovation Award 2024Partners Life - Insurer Innovation Award 2024
Partners Life - Insurer Innovation Award 2024
 
Exploring the Future Potential of AI-Enabled Smartphone Processors
Exploring the Future Potential of AI-Enabled Smartphone ProcessorsExploring the Future Potential of AI-Enabled Smartphone Processors
Exploring the Future Potential of AI-Enabled Smartphone Processors
 
How to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected WorkerHow to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected Worker
 
2024: Domino Containers - The Next Step. News from the Domino Container commu...
2024: Domino Containers - The Next Step. News from the Domino Container commu...2024: Domino Containers - The Next Step. News from the Domino Container commu...
2024: Domino Containers - The Next Step. News from the Domino Container commu...
 
IAC 2024 - IA Fast Track to Search Focused AI Solutions
IAC 2024 - IA Fast Track to Search Focused AI SolutionsIAC 2024 - IA Fast Track to Search Focused AI Solutions
IAC 2024 - IA Fast Track to Search Focused AI Solutions
 
🐬 The future of MySQL is Postgres 🐘
🐬  The future of MySQL is Postgres   🐘🐬  The future of MySQL is Postgres   🐘
🐬 The future of MySQL is Postgres 🐘
 
From Event to Action: Accelerate Your Decision Making with Real-Time Automation
From Event to Action: Accelerate Your Decision Making with Real-Time AutomationFrom Event to Action: Accelerate Your Decision Making with Real-Time Automation
From Event to Action: Accelerate Your Decision Making with Real-Time Automation
 
Apidays Singapore 2024 - Building Digital Trust in a Digital Economy by Veron...
Apidays Singapore 2024 - Building Digital Trust in a Digital Economy by Veron...Apidays Singapore 2024 - Building Digital Trust in a Digital Economy by Veron...
Apidays Singapore 2024 - Building Digital Trust in a Digital Economy by Veron...
 

Resource wrappers in C++

  • 1. Resource wrappers Ilio Catallo - info@iliocatallo.it
  • 2. Outline • The importance of std::vector • Representa2on • Ini2aliza2on • Accessing elements • Destruc2on
  • 3. Outline • Copying • Assignment • Rule of three • Bibliography
  • 5. The ubiquity of std::vector The most useful container in the C++ standard library is std::vector
  • 6. Why std::vector is so useful std::vector is built from low-level memory management facili5es, such as pointers and arrays
  • 7. Why std::vector is so useful std::vector's primary role is to help us avoid the complexi4es of those facili4es
  • 8. Why std::vector is so useful That is, std::vector is designed to insulate us from some of the unpleasant aspects of real memory
  • 9. Building std::vector In the remainder, we will write a simplified std::vector implementa0on
  • 10. Building std::vector Namely, we will write a non-resizable version of std::vector for the limited case of int elements
  • 11. The vector_int class We will refer to this type as vector_int
  • 12. Why bother re-inven.ng the wheel when we already have std::vector?
  • 13. But why? The reason is that re-implemen0ng std::vector allows us to prac0ce many basic language facili0es at once • Pointers & arrays • Classes & operator overloading
  • 14. But why? Secondly, it allows stressing that whenever we design a class, we must consider ini#aliza#on, copying and destruc#on1 1 For simplicity, in the remainder we are going to deliberately ignore move seman8cs, i.e., we are going to discuss object lifecycle as of C++03
  • 15. But why? Finally, as computer scien2sts we need to know how to design and implement abstrac2ons such as std::vector
  • 16. Objec&ves Ideally, we would like to achieve the following: void f() { vector_int v(7); // varying number of elements v[0] = 7; // writing elements std::cout << v.size(); // it knows its size std::cout << v[1]; // values are default initialized vector_int w = v; // copy construction vector_int u; u = v; // assignment // automatic memory management // (no delete[] required) }
  • 17. Natural behavior vector_int provides us opera,ons that seem natural from the viewpoint of a user, rather than from the viewpoint of hardware
  • 18. Natural behavior We want to get to the point where we can program using types that provide exactly the proper4es we want based on logical needs
  • 19. Natural behavior To get there, we have to overcome a number of fundamental constraints related to access to the bare machine, such as: • An object in memory is of fixed size • An object in memory is in one specific place
  • 21. Designing vector_int We start our incremental design of vector_int by considering a very simple use: vector_int grades(4); grades[0] = 28; grades[1] = 27; grades[2] = 30: grades[3] = 25;
  • 22. Designing vector_int This creates a vector_int with four elements and give those elements the values 28, 27, 30, 25 vector_int grades(4); grades[0] = 28; grades[1] = 27; grades[2] = 30: grades[3] = 25;
  • 23. The size of a vector_int The number of elements of a vector_int is called its size vector_int grades(4); grades[0] = 28; grades[1] = 27; grades[2] = 30: grades[3] = 25;
  • 24. The size of a vector_int Therefore, the size of grades is four vector_int grades(4); grades[0] = 28; grades[1] = 27; grades[2] = 30: grades[3] = 25;
  • 25. The size of a vector_int Moreover, the number of elements of a vector_int are indexed from 0 to size - 1 vector_int grades(4); grades[0] = 28; grades[1] = 27; grades[2] = 30: grades[3] = 25;
  • 26. Represen'ng grades Graphically, we can represent grades like: grades[0] grades[1] grades[2] grades[3] ┌───────────┬───────────┬──────────┬───────────┐ │ │ │ │ │ └───────────┴───────────┴──────────┴───────────┘ ▲ ┌─────────┘ │ │ ┌─────┬─────┐ grades: │ 4 │ │ └─────┴─────┘
  • 27. How do we implement that drawing in code?
  • 28. vector_int is a class Unsurprisingly, we have to define a class and call it vector_int class vector_int { ... };
  • 29. Data members Obviously, we cannot design vector_int to have a fixed number of elements class vector_int { public: ... private: int elem0, elem1, elem2, elem3; };
  • 30. Data members Conversely, we need a data member that points to the sequence of elements class vector_int { public: ... private: int* elem; };
  • 31. Data members That is, we need a pointer member to the sequence of elements class vector_int { public: ... private: int* elem; };
  • 32. Data members Furthermore, we need a data member to hold the size of the sequence class vector_int { public: ... private: std::size_t sz; int* elem; };
  • 33. grades representa)on Therefore, a refined grades representa0on is as follows: elem[0] elem[1] elem[2] elem[3] ┌───────────┬───────────┬──────────┬───────────┐ │ │ │ │ │ int[4] └───────────┴───────────┴──────────┴───────────┘ ▲ ┌─────────┘ │ sz elem ┌─────┬─────┐ grades: │ 4 │ │ vector_int └─────┴─────┘
  • 35. Ini$aliza$on At construc*on *me, we would like to allocate sufficient space for the elements on the heap // v allocates 4 ints on the heap vector_int v(4);
  • 36. Ini$aliza$on Elements will be later accessed through the data member v.elem class vector_int { public: ... private: std::size_t sz; int* elem; };
  • 37. Alloca&ng space To this end, we use new in the constructor to allocate space for the elements, and we let elem point to the address returned by new class vector_int { public: vector_int(std::size_t sz): elem(...) {} ... }
  • 38. Alloca&ng space To this end, we use new in the constructor to allocate space for the elements, and we let elem point to the address returned by new class vector_int { public: vector_int(std::size_t sz): elem(new int[sz]) {} ... }
  • 39. Storing the size Moreover, since there is no way to recover the size of the dynamic array from elem, we store it in sz class vector_int { public: vector_int(std::size_t sz): sz(sz), elem(new int[sz]) {} ... };
  • 40. Storing the size We want users of vector_int to be able to get the number of elements
  • 41. size() access func)on Hence, we provide an access func2on size() that returns the number of elements class vector_int { public: vector_int(std::size_t sz): sz(sz), elem(new int[sz]) {} std::size_t size() const { return sz; } private: std::size_t sz; int* elem; };
  • 42. Sensible ini)aliza)on In addi'on, we would like to ini'alize the newly-allocated elements to a sensible value vector_int v(3); std::cout << v[0]; // output: 0 (as opposed to some random values)
  • 43. Sensible ini)aliza)on Again, we can do that at construc2on 2me vector_int v(3); std::cout << v[0]; // output: 0 (as opposed to some random values)
  • 44. Sensible ini)aliza)on class vector_int { public: vector_int(std::size_t sz): sz(sz), elem(new int[sz]) { for (std::size_t i = 0; i < sz; ++i) elem[i] = 0; } std::size_t size() const { return sz; } private: std::size_t sz; int* elem; };
  • 45. Default constructor Moreover, in the case of containers, we have a natural default valid state, i.e., the empty container
  • 46. Default constructor Hence, we can introduce a default constructor class vector_int { public: vector_int(): sz(0), elem(nullptr) {} vector_int(std::size_t sz): sz(sz), elem(new int[sz]) {...} private: std::size_t sz; int* elem; };
  • 48. vector_int as a pointer Note that – up to this point – vector_int values are just pointers that remember the size of the pointed array vector_int v(4); std::cout << v.size(); // output: 4
  • 49. Accessing elements However, for a vector_int to be usable, we need a way to read and write elements vector_int v(4); v[0] = 1; // writing elements std::cout << v[0]; // reading elements
  • 50. Accessing elements That is, we would like to add the possibility of access elements through our usual subscript nota-on vector_int v(4); v[0] = 1; // writing elements std::cout << v[0]; // reading elements
  • 51. Operator func-on The way to get that is to define a member operator func,on operator[] vector_int v(4); v[0] = 1; // writing elements std::cout << v[0]; // reading elements
  • 52. Defining operator[] How can we define the operator[] overload? class vector_int { public: ... ??? operator[](std::size_t i) { ??? } private: std::size_t sz; int* elem; };
  • 53. Defining operator[] Intui&vely, one would say class vector_int { public: ... int operator[](std::size_t i) { return elem[i]; } private: std::size_t sz; int* elem; };
  • 54. Defining operator[] Apparently, we can now manipulate elements in a vector_int vector_int v(10); int x = v[2]; std::cout << x; // output: 0
  • 55. Naive implementa,on This looks good and simple, but unfortunately it is too simple int operator[](std::size_t i) { return elem[i]; }
  • 56. Naive implementa,on What happens when we write the following? vector_int v(10); v[3] = 7;
  • 57. Naive implementa,on We get a compile-)me error :-( vector_int v(10); v[3] = 7; // compile-time error!
  • 58. Returning a value Our implementa-on of operator[] returns a temporary of type int. Hence, we cannot assign any addi-onal value to it vector_int v(10); v[3] = 7;
  • 59. Returning a value That is, le+ng the subscript operator return a value enables reading but not wri6ng elements int operator[](std::size_t i) { return elem[i]; }
  • 60. Returning a reference Returning a reference, rather than a value, from the subscript operator solves this problem int& operator[](std::size_t i) { return elem[i]; }
  • 61. Returning a reference class vector_int { public: ... int& operator[](std::size_t i) { return elem[i]; } private: std::size_t sz; int* elem; };
  • 62. Constant vector_ints Our subscript operator has s/ll a problem, namely, it cannot be invoked for constant vector_ints void p(vector_int const& v) { int x = v[0]; }
  • 63. Constant vector_ints This is because operator[] has not been marked as const void p(vector_int const& v) { int x = v[0]; }
  • 64. Constant vector_ints However, we cannot simply mark operator[] as const, as it will prevent any further modifica9on to its elements void p(vector_int const& v) { int x = v[0]; }
  • 65. Const-overload To solve this, we provide an addi$onal overload of operator[] specifically meant for accessing const vectors class vector_int { public: ... int& operator[](std::size_t i) { return elem[i]; } int operator[](std::size_t i) const { return elem[i]; } private: std::size_t sz; int* elem; };
  • 66. Const-overload We can now access elements of constant vector_ints without preven4ng the possibility of modifying non-const vector_ints class vector_int { public: ... int& operator[](std::size_t i) { return elem[i]; } int operator[](std::size_t i) const { return elem[i]; } private: std::size_t sz; int* elem; };
  • 68. Resource acquisi,on No#ce that in the constructor we allocate memory for the elements using new class vector_int { public: vector_int(std::size_t sz): sz(sz), elem(new int[sz]) {} ... };
  • 69. Memory leak However, when leaving f(), the heap-allocated elements pointed to by v.elem are not released void f() { vector_int v(10); int e = v[0]; };
  • 70. Memory leak We are therefore causing a memory leak void f() { vector_int v(10); int e = v[0]; };
  • 71. Fixing the leak To solve this, we must make sure that this memory is freed using delete[] void f() { vector_int v(10); int e = v[0]; // we should call delete[] before // returning from f() };
  • 72. The clean_up() member We could define a clean_up() member func0on class vector_int { public: vector_int(std::size_t sz): sz(sz), elem(new int[sz]) {...} void clean_up() { delete[] elem; } ... private: std::size_t sz; int* elem; };
  • 73. The clean_up() member We can then call clean_up() as follows void f() { vector_int v(10); int e = v[0]; v.clean_up(); };
  • 74. Releasing the memory Although that would work, one of the most common problems with the heap is that it is extremely easy to forget to call delete void f() { vector_int v(10); int e = v[0]; v.clean_up(); };
  • 75. Releasing the memory The equivalent problem would arise for clean_up() void f() { vector_int v(10); int e = v[0]; v.clean_up(); };
  • 76. Destructors Fortunately, C++ provides facili6es that allow us to do be:er than that
  • 77. Destructors In par'cular, in C++ each class is provided with a special func,on that makes sure that an object is properly cleaned up before it is destroyed
  • 78. Destructors Such a special func.on is called the destructor
  • 79. Destructors The destructor is a public member func-ons with no input parameters and no return type class vector_int { public: ~vector_int() {...} ... };
  • 80. Destructors The destructor has the same name as the class, but with a !lde (~) in front of it class vector_int { public: ~vector_int() {...} ... };
  • 81. Implemen'ng ~vector_int() class vector_int { public: vector_int(std::size_t sz): sz(sz), elem(new int[sz]) {...} ~vector_int() { ??? } ... private: std::size_t sz; int* elem; };
  • 82. Implemen'ng ~vector_int() class vector_int { public: vector_int(std::size_t sz): sz(sz), elem(new int[sz]) {...} ~vector_int() { delete[] elem; } ... private: std::size_t sz; int* elem; };
  • 83. Automa'c memory dealloca'on Thanks to the presence of the destructor, we do not need to explicitly release memory void f() { vector_int v(10); int e = v[0]; // Hurray! the dynamic arrays pointed to // by v.elem is automatically deleted };
  • 84. Automa'c memory dealloca'on The tremendous advantage is that a vector cannot forget to call its destructor to deallocate the memory used for its elements
  • 85. Synthesized destructors If we do not explicitly provide a destructor, the compiler will generate a synthesized destructor
  • 86. Synthesized destructors Such a synthesized destructor invokes the destructors for the elements (if they have destructors)
  • 87. vector_int synth. destructor Since sz and elem do not provide a destructor, the synthesized destructor would simply reduce to an empty func8on class vector_int { public: // synthesized destructor ~vector_int() {} ... private: std::size_t sz; int* elem; };
  • 88. vector_int synth. destructor This is why we end up with a memory leak if we do not explicitly specify a destructor for vector_int class vector_int { public: // synthesized destructor ~vector_int() {} ... private: std::size_t sz; int* elem; };
  • 90. Copying two vector_ints Let us try to copy one vector_int into another void h() { vector_int v(3); v[0] = 7; v[1] = 3; v[2] = 8; vector_int w = v; // what happens? }
  • 91. Copying two vector_ints Ideally we would like w to become a copy of v void h() { vector_int v(3); v[0] = 7; v[1] = 3; v[2] = 8; vector_int w = v; // what happens? }
  • 92. Copy seman+cs That means: • w.size() == v.size() • w[i] == v[i] for all i's in [0:v.size()) • &w[i] != &v[i] for all i's in [0:v.size())
  • 93. Copy seman+cs ┌───────────┬───────────┬───────────┐ │ 7 │ 3 │ 8 │ int[3] └───────────┴───────────┴───────────┘ ▲ ┌─────────┘ │ sz elem ┌─────┬─────┐ v: │ 3 │ │ vector_int └─────┴─────┘ sz elem ┌─────┬─────┐ w: │ 3 │ │ vector_int └─────┴─────┘ │ └──────────┐ │ ▼ ┌───────────┬───────────┬──────────┐ │ 7 │ 3 │ 8 │ int[3] └───────────┴───────────┴──────────┘
  • 94. Copy seman+cs Furthermore, we want all memory to be released once returning from h() void h() { vector_int v(3); v[0] = 7; v[1] = 3; v[2] = 8; vector_int w = v; // here, v.elem and w.elem get released }
  • 95. Wrong copy behavior Unfortunately, this is not what happens with our vector_int implementa4on
  • 96. Copying is ini*alizing First, note that w is ini#alized from v void h() { vector_int v(3); v[0] = 7; v[1] = 3; v[2] = 8; vector_int w = v; }
  • 97. Copying is ini*alizing We know that ini#aliza#on is done by a constructor void h() { vector_int v(3); v[0] = 7; v[1] = 3; v[2] = 8; vector_int w = v; }
  • 98. Copying is ini*alizing Hence, we have to conclude that vector_int provides a constructor that accepts vector_ints as its only input argument vector_int(vector_int const& v) {...}
  • 99. Copying is ini*alizing This is made even more evident if we use an equivalent nota4on for the last line in h() void h() { vector_int v(3); v[0] = 7; v[1] = 3; v[2] = 8; vector_int w(v); // the same as: vector_int w = v; }
  • 100. Copying is ini*alizing However, we never wrote such a constructor
  • 101. Copy constructor It turns out that every class in C++ is provided with a special constructor called copy constructor vector_int(vector_int const& v) {...}
  • 102. Copy constructor A copy constructor is defined to take as its argument a constant reference to the object from which to copy vector_int(vector_int const& v) {...}
  • 103. vector_int's constructors class vector_int { public: vector_int(): sz(0), elem(nullptr) {} vector_int(std::size_t sz): sz(sz), elem(new int[sz]) {...} vector_int(vector_int const& v) { ... } private: std::size_t sz; int* elem; };
  • 104. Const-reference Note that the v is passed by reference vector_int(vector_int const& v) {...}
  • 105. Const-reference Why don't we pass v by value? vector_int(vector_int const& v) {...}
  • 106. Const-reference Passing by value would require v to be copied... vector_int(vector_int const& v) {...}
  • 107. Const-reference ...which would in turn cause the invoca2on of v's copy constructor... vector_int(vector_int const& v) {...}
  • 108. Const-reference ...thus leading to an infinite loop! vector_int(vector_int const& v) {...}
  • 109. Synthesized copy constructor If we do not provide an explicit copy constructor, the compiler will generate a synthesized one for us
  • 110. Synthesized copy constructor Synthesized copy constructors simply perform memberwise copy class vector_int { public: // synthesized copy constructor vector_int(vector_int const& v): sz(v.sz), elem(v.elem) {} ... private: std::size_t sz; int* elem; };
  • 111. Copying pointers However, memberwise copying in the presence of pointer members (such as elem) usually causes problems // synthesized copy constructor vector_int(vector_int const& v): sz(v.sz), elem(v.elem) {}
  • 112. Copying pointers Specifically, w ends up sharing v's elements void h() { vector_int v(3); v[0] = 7; v[1] = 3; v[2] = 8; vector_int w = v; }
  • 113. Sharing elements sz elem ┌─────┬─────┐ w: │ 3 │ │ vector_int └─────┴─────┘ │ └──────────┐ │ ▼ ┌───────────┬───────────┬───────────┐ │ 7 │ 3 │ 8 │ int[3] └───────────┴───────────┴───────────┘ ▲ ┌─────────┘ │ sz elem ┌─────┬─────┐ v: │ 3 │ │ vector_int └─────┴─────┘
  • 114. Sharing elements What is the output? vector_int v(3); v[0] = 7; v[1] = 3; v[2] = 8; vector_int w = v; w[0] = 10; std::cout << v[0];
  • 115. Sharing elements What is the output? vector_int v(3); v[0] = 7; v[1] = 3; v[2] = 8; vector_int w = v; w[0] = 10; std::cout << v[0]; // output: 10
  • 116. Double dele)on Moreover, when we return from h() the destructors for v and w are implicitly called void h() { vector_int v(3); v[0] = 7; v[1] = 3; v[2] = 8; vector_int w = v; }
  • 117. Double dele)on Due to their hidden connec-on, both v and w will try to release the same memory area
  • 118. A working copy constructor Hence, we need to explicitly provide a copy constructor that will 1. set the number of elements (i.e., the size) 2. allocate memory for its elements 3. copying the elements from the source vector_int
  • 119. A working copy constructor class vector_int { public: vector_int(vector_int const& v): sz(v.sz), elem(new int[v.sz]) { std::copy(v.elem, v.elem + sz, elem); } ... private: std::size_t sz; int* elem; };
  • 120. A working copy constructor void h() { vector_int v(3); v[0] = 7; v[1] = 3; v[2] = 8; vector_int w = v; }
  • 121. A working copy constructor ┌───────────┬───────────┬───────────┐ │ 7 │ 3 │ 8 │ int[3] └───────────┴───────────┴───────────┘ ▲ ┌─────────┘ │ sz elem ┌─────┬─────┐ v: │ 3 │ │ vector_int └─────┴─────┘ sz elem ┌─────┬─────┐ w: │ 3 │ │ vector_int └─────┴─────┘ │ └──────────┐ │ ▼ ┌───────────┬───────────┬──────────┐ │ 7 │ 3 │ 8 │ int[3] └───────────┴───────────┴──────────┘
  • 122. No more double dele+on Given that the two vector_ints are now independent, the two destructors can do the right thing void h() { vector_int v(3); v[0] = 7; v[1] = 3; v[2] = 8; vector_int w = v; }
  • 124. Assignment Even though we now correctly handle copy construc4on, vector_ints can s4ll be copied by assignment void g() { vector_int v(3); v[0] = 7; v[1] = 3; v[2] = 8; vector_int w(4); w = v; }
  • 125. Assignment What happens when we execute the following? void g() { vector_int v(3); v[0] = 7; v[1] = 3; v[2] = 8; vector_int w(4); w = v; // what happens? }
  • 126. Memory leak and double dele/on Unfortunately, we once again end up with a memory leak and a double dele-on void g() { vector_int v(3); v[0] = 7; v[1] = 3; v[2] = 8; vector_int w(4); w = v; }
  • 127. Synthesized assignment operator As a ma&er of fact, if we do not provide any overload for the assignment operator, the compiler will synthesize one for us
  • 128. Synthesized assignment operator Synth. assignment operators perform memberwise assignment class vector_int { public: // synthesized assignment operator vector_int& operator=(vector_int const& v) { sz = v.sz; elem = v.elem; return *this; } ... private: std::size_t sz; int* elem; };
  • 129. Synthesized assignment operator ┌───────────┬───────────┬───────────┬───────────┐ │ 0 │ 0 │ 0 │ 0 │ int[4] └───────────┴───────────┴───────────┴───────────┘ ▲ ┌───────┘ │ │ sz elem ┌─────┬─────┐ w: │ 4 │ │ vector_int └─────┴─────┘ ┌───────────┬───────────┬───────────┐ │ 7 │ 3 │ 8 │ int[3] └───────────┴───────────┴───────────┘ ▲ ┌─────────┘ │ sz elem ┌─────┬─────┐ v: │ 3 │ │ vector_int └─────┴─────┘
  • 130. Synthesized assignment operator ┌───────────┬───────────┬───────────┬───────────┐ │ 0 │ 0 │ 0 │ 0 │ int[4] └───────────┴───────────┴───────────┴───────────┘ sz elem ┌─────┬─────┐ w: │ 3 │ │ vector_int └─────┴─────┘ │ └──────────┐ │ ▼ ┌───────────┬───────────┬───────────┐ │ 7 │ 3 │ 8 │ int[3] └───────────┴───────────┴───────────┘ ▲ ┌─────────┘ │ sz elem ┌─────┬─────┐ v: │ 3 │ │ vector_int └─────┴─────┘
  • 131. Synth. assignment for vector_int Since we did not provide an explicit overload for operator=, the synthesized assignment is used vector_int& operator=(vector_int const& v) {...}
  • 132. Working assignment operator The remedy for the assignment is fundamentally the same as for the copy constructor vector_int& operator=(vector_int const& v) {...}
  • 133. Working assignment operator class vector_int { public: vector_int& operator=(vector_int const& v) { int* p = new int[v.sz]; std::copy(v.elem, v.elem + v.sz, p); delete[] elem; elem = p; sz = v.sz; return *this; } ... private: std::size_t sz; int* elem; };
  • 135. Destructors are fundamental Destructors are conceptually simple but are the founda'on for many of the most effec5ve C++ programming techniques
  • 136. Destructors are fundamental The usage of the constructor / destructor pair for correctly managing heap-allocated memory is the archetypical example
  • 137. Resource wrappers need destructors More generally, a class needs a destructor if it acquires resources
  • 138. Resource A resource is something we get from somewhere and that we must give back once we have finished using it • Memory • File descriptor • Socket • ...
  • 139. The big three A class that needs a destructor almost always needs a copy construc-on and assignment
  • 140. The big three The reason is that if an object has acquired a resource the default meaning of copy is almost certainly wrong
  • 141. The rule of three This is summarised in the so-called rule of three2 If you need to explicitly declare either the destructor, copy constructor or assignment operator yourself, you probably need to explicitly declare all three of them 2 Once again, we are limi/ng ourselves to C++03, in C++11 one would obey either the rule of five or the rule of zero
  • 143. Bibliography • B. Stroustrup, The C++ Programming Language (4th ed.) • B, Stroustrup, Programming: Principles and Prac@ce Using C++ (2nd ed.) • A. Stepanov, Notes on programming • StackOverflow FAQ, What is the rule of three?