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:

  • Allows us to declare that the following is parameterized over the typeT. So inside the part that follows,Trefers to a typename that we can specify in our client code.

  • The Node<SOME_TYPE_GOES_HERE> is a template specialization

  • Name 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:

The Standard Template Library (STL)

  • Has a large # of useful templates eg. dynamic-length arrays: ```vector

Vector Operations

Indexing

  • the v[i] is the ith element of v

  • Going 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:

Stack Unwinding

Consider the following:

What happens in the above code?

  1. main calls h

  2. h calls g

  3. g calls f

  4. f raises out_of_range

  5. 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.

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

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?