CS 202 Fall 2013 > Notes for Thursday, October 31, 2013 |
CS 202 Fall 2013
Notes for Thursday, October 31, 2013
Exceptions (cont'd) [16.1]
Standard Exception Types
The C++ Standard Library defines a number
of exception types.
All of these are derived from base class
std::exception
.
Most of the derived types are declared in
standard header <stdexcept>
.
See documentation for the C++ Standard Library for
more information on standard exception types.
You may define and use your own exception types. If you wish, you may make these derived classes of standard exception types (all of which have virtual destructors).
[C++]
#include <exception> using std::exception; class UphappyWidget : public exception { ~~~ };
Throwing
Last time we caught exceptions. We can also throw them. We do this when our code encounters an error condition that it cannot handle. When we throw an exception to signal the caller that there is something amiss.
[C++]
~~~ if (widget.is_unhappy()) { throw UnhappyWidget(); } ~~~
Note that when we write a throw
statement,
we are passing an error condition on to some other code.
Usually someone else writes the catch
blocks.
We almost never write both a throw and a catch.
Catch-All & Re-Throw
We can catch all exceptions by putting “...
”
inside the parentheses after catch
.
In this case, we do not get to look at the exception object.
[C++]
catch (...)
Inside a catch
block, we can re-throw
the same exception using throw
with no parameters.
[C++]
throw;
The above two features of C++ are almost always used together.
Suppose we make a function call that might throw.
If it does, we cannot handle the error,
but we need to do some clean-up work before passing
the exception on to our caller.
In this case, we call the function inside a try
block, then catch-all, do the clean-up, and re-throw.
[C++]
~~~ try { foo(); // Might throw } catch (...) // Catch-all { clean_up(); // Whatever we need to do throw; // We cannot handle the error here; send it on } ~~~
Throwing & Catching: Philosophy
We use an exception to signal an error condition. Thus:
- We throw in code that is unable to handle some error condition.
- We catch in code that can handle the error condition.
- We do catch-all & re-throw when we need to do some clean-up before leaving a block of code due to an exception.
As noted above, we almost never write both a throw and a catch
(except when we do catch-all & re-throw).
Rather, it is often the author of a package who
writes a throw
statatement
to inform the caller that the package has an error it cannot handle.
And it is often the author of code that uses a package
who writes a catch
block to handle an error
enountered in the package.
This also explains why it is a good idea to indicate the kind of error by the type of the object thrown. We catch an exception by its type. We catch a certain type when we can handle the kind of error it signals.
As stated last time, I recommend that you catch exceptions by reference.
[C++]
try { ~~~ } catch (std::runtime_error & e) // "&" for catch by reference { ~~~
There are two reasons for this.
- First, if we catch by value, then we do an unnecessary copy of the exception object. Catching by reference avoids this.
- Second, when we catch by reference, we can handle exceptions polymorphically. Catch a base-class object, and you get derived-class objects, too. This means that a base-class exception object can indicate a whole category of error condtions, while each derived class indicates a more specific error condition. Catch the type that indicates the most general kind of error that you can handle.
Function Templates [16.2]
Introduction to Templates
A C++ template is a way of writing code that does not specify what types it is used with. Templates enable generic programming, in which we write data structures and algorithms that can handle arbitrary types of data.
A class template
tells the compiler how to write a class using
some type that the compiler figures out.
Using such a template,
the compiler can write one or more actual classes.
For example,
vector
is a class template,
while vector<int>
is a
class that a compiler can write based on the vector
template.
Now we look at function templates, which allow compilers to write functions in a similar manner.
Writing Function Templates
Here is a function that prints an int
with a message.
[C++]
void likeBetter(int n, int x) { cout << "Here is something I like " << n << " times as much as I like you: " << x << endl; int xx = x + x; cout << "And here is what I get when I add it to itself: " << xx << endl; }
We can call the above function passing a double
for x
, but this will convert the double
to an int
.
If we do not want this to happen,
we could overload the function with a separate double
version.
[C++]
void likeBetter(int n, double x) { cout << "Here is something I like " << n << " times as much as I like you: " << x << endl; double xx = x + x; cout << "And here is what I get when I add it to itself: " << xx << endl; }
And then we could write another version for a string
or any other type.
Using a function template we can write all of these at once.
[C++]
template<typename PrintType> void likeBetter(int n, PrintType x) { cout << "Here is something I like " << n << " times as much as I like you: " << x << endl; PrintType xx = x + x; cout << "And here is what I get when I add it to itself: " << xx << endl; }
Here, PrintType
is a template parameter.
It is a type that the compiler gets to choose.
Note that we do not change every instance of int
to PrintType
,
but only the ones we want the compiler to choose.
Calling Function Templates
We call a function template just like an ordinary function. The compiler determines what to do with the template parameters and writes the required function based on the template.
[C++]
int x1 = 42; likeBetter(3, x1); string x2 = "chocolate ice cream"; likeBetter(100, x2);
Overloading with Ordinary Functions
We can write an ordinary function that will be used in preference to a template when the parameter types match.
[C++]
void likeBetter(int n, double x) { cout << "Here is a FLOATING-POINT NUMBER I like " << n << " times as much as I like you: " << x << endl; double xx = x + x; cout << "And here is what I get when I add it to itself: " << xx << endl; cout << "WOW!!!!!!!!!!!!!!!!!!!!!!" <<endl; }
Now we can call both template and non-template versions using different parameter types.
[C++]
int x1 = 42; likeBetter(3, x1); // Calls template version double x3 = 3.7; likeBetter(4, x3); // Calls non-template version
See
functemp.cpp
for a program that includes a function template.
For today’s lab work, see the 10/31 Challenge.