Lecture 17
Templates
Class parametrized by a type
Used to store other things - like new classes
Put implementation in
.h
files
Consider the linked list implementation:
class Node {
int data;
Node *next;
public:
Node(int data, Node *next): data(data), next(next) {}
~Node() { delete next; }
int getData() { return data; }
Node *getNode() { return next; }
};
int main() {
Node *ll = new Node(1, new Node(2, 0));
// ...
delete ll;
}
Limitations of this implementation: Can only hold a list of ints How to improve this implementation: If we want to store other types, must use void *
pointers or templates
Consider the linked list implementation with TEMPLATES:
template<typename T> class Node {
T data; // this data is of type `T`
Node<T> *next; // `Node<T>` refers to this class parameterized over the same type
public:
Node(T data, Node<T> *next): data(data), next(next) {}
~Node() { delete next; }
T getData() { return data; }
Node<T> *getNode() { return next; }
}
int main() {
Node<char> *ll = new Node<char>('a', new Node<char>('b', 0))
// `ll` is a linked list of characters
// ...
delete ll;
}
Allows us to declare that the following is parameterized over the type
T
. So inside the part that follows,T
refers to a typename that we can specify in our client code.The
Node<SOME_TYPE_GOES_HERE>
is a template specializationName of the class is still Node, but we still need to write when it is ambiguous what type T might be
Allows us to have a linked list of whatever type we want.
When encountering a template specialization like Node, compiler creates in memory a new class identical to Node with T replaced with the actual type (similar to the preprocessor, but more powerful, and also typesafe)
i.e. Compilier specializes templates at the source code level, before compilation.
Note: We can even do Node< Node<int> >
or even more nested structures. But, note that Node<Node<int>>
doesn't compile because >>
is a right shift operator, so we need to add a space.
In-Class Template Example:
template <typename T> class List {
struct Node {
T data;
Node *next;
};
Node *therList;
public:
class Iterator {
Node *p;
explicit Iterator (Node *p): p{p} {}
public:
T &operator*() {
return p->data;
}
};
T &ith (int i) {...}
void addToFront (T x) {...}
};
// Client
List <int> l1;
List <List<int>> l2;
l1.addToFront(3);
l2.addToFront(l1);
for (List<int)::Iterator it.l1.begin(); it != l1.end(); ++it) {
cout << *it << endl;
}
for (auto n: l1) {
...
}
The Standard Template Library (STL)
Has a large # of useful templates eg. dynamic-length arrays: ```vector
Vector Operations
#include <vector>
using namespace std;
vector<int> v {4, 5}; // creates vector 4 5
v.emplace_back(6); // 4 5 6
v.emplace_back(7) // 4 5 6 7
// Note:
vector<int> v(4,5); // only case in course where round bracket and curly bracket initialization are different
// the above code produces 5 5 5 5 instead of 4 5
// Looping vector with no range check
for (int i=0; i < v.size(); ++i) {
cout << v[i] << endl;
}
// array access with range checking (impossible to get out of bounds error)
for (int i = 0; i < v.size(); ++i) {
std::cout << v.at(i) << std::endl;
}
// RECOMMENDED WAY OF ITERATING
for (vector<int>::iterator it = begin(); it != v.endl(); ++it) {
cout << *it << endl;
} // note here that it's iterator not Iterator
// the above code can of course be replaced by auto
for (auto n:v) {...}
// Iterating in reverse
for (vector<int>::reverse_iterator it = v.rbegin(); it != v.rend(); ++it)
v.pop_back() // removes last element
// Use iterators to remove items from inside a vector:
auto it = v.erase(v.begin()); // erases the first item
it = v.erase(v.begin() + 3); // erases the 4th item, returns iterator pointing to first item after the erase
it = erase(it);
v,erase(v.end()-1) // last item
Indexing
the
v[i]
is the ith element of vGoing out of bounds is undefined behaviour
Use
v.at(i)
Checked version of
v[i]
So what happens when you go out of bounds?
What should happen? (Raise an Exception)
Problem:
vector's code can detect the error, but doesn't know what to do about it
client can respond, but can't detect the error
C Solution: functions return a status code or set the global variable errno
leads to awkward programming
encourages programmers to ignore error-checks
C++ Solution: when error arises, the function raises an exception What happens? By default, execution stops
Exception (Try and Catch)
But we can write handlers to catch extensions to deal with out of bounds. .vector<T>::at
throws std::out_of_range
when it fails. We can handle is as follows:
#include <stdexcept>
...
try {
cout << v.at(10000);
} catch (out_of_range r) {
cerr << "Range error: " << r.what() << endl;
}
// execution resumes here
Stack Unwinding
Consider the following:
void f() {
throw out_of_range {"f"}; // ctor argument = what
}
void g() {
f();
}
void h() {
g();
}
int main() {
try {
h();
} catch (out_of_range) {...} // r does not have to be here
}
What happens in the above code?
main calls h
h calls g
g calls f
f raises out_of_range
Control goes back through the call chain (unwinds the stack) until handler (the catch) is found, in this case, back to main (where main handles exception)
If there is matching handler in the entire call chain => program terminates
A handler might do part of the recovery job, i.e. execute some corrective code and throw another exception.
try {...}
catch (SomeErrorType s) {
...
throw SomeOtherError {...};
}
// or rethrow the same exception:
try {...}
catch (SomeErrorType s) {
...
throw;
}
Throw
throw; vs. throw s {...};
(from code above):
.throw s {...};
s may be a subtype of SomeErrorType
throws a new exception of type SomeErrorType
.throw;
actual type of s is retained
In the end, it's better to just say throw;
Catching all Errors
A handler can act as a catch-all
try {...}
catch (...) { // catches all exceptions (it's actually "..." in the brackets after catch)
...
}
Note: You can throw anything you want - Don't always have to throw objects
Never let a destuctor throw an exception
Why not?
Program will abort immediately (defined behaviour)
If you want a destructor to throw, then tag it with
noexcept(false)
But - if a destructor is running during stack unwinding, while dealing with another exception and it throws, you now have 2 active unhandled exceptions and the program will abort immediately
Last updated
Was this helpful?