Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Professional C++ [eng].pdf
Скачиваний:
284
Добавлен:
16.08.2013
Размер:
11.09 Mб
Скачать

Chapter 15

The C++ standard library defines eight exception classes, which are described in more detail below. You can also write your own exception classes. How to do so is also detailed below.

Catching Exception Objects by Const and Reference

In the example above in which readIntegerFile() throws an object of type exception, the catch line looks like this:

} catch (const exception& e) {

However, there is no requirement to catch objects by const reference. You could catch the object by value like this:

} catch (exception e) {

Alternatively, you could catch the object by reference (without the const):

} catch (exception& e) {

Also, as you saw in the char* example, you can catch pointers to exceptions, as long as pointers to exceptions are thrown.

Your programs can catch exceptions by value, reference, const reference, or pointer.

Throwing and Catching Multiple Exceptions

Failure to open the file is not the only problem readIntegerFile() could encounter. Reading the data from the file can cause an error if it is formatted incorrectly. Here is an implementation of readIntegerFile() that throws an exception if it cannot either open the file or read the data correctly.

void readIntegerFile(const string& fileName, vector<int>& dest)

{

ifstream istr; int temp;

istr.open(fileName.c_str()); if (istr.fail()) {

// We failed to open the file: throw an exception. throw exception();

}

// Read the integers one by one and add them to the vector. while (istr >> temp) {

dest.push_back(temp);

}

if (istr.eof()) {

//We reached the end-of-file. istr.close();

}else {

//Some other error. Throw an exception.

408

Handling Errors

istr.close(); throw exception();

}

}

Your code in main() does not need to change because it already catches an exception of type exception. However, that exception could now be thrown in two different situations, so you should modify the error message accordingly:

int main(int argc, char** argv)

{

//Code omitted try {

readIntegerFile(fileName, myInts); } catch (const exception& e) {

cerr << “Unable either to open or to read “ << fileName << endl; exit (1);

}

//Code omitted

}

Alternatively, you could throw two different types of exceptions from readIntegerFile(), so that the caller can tell which error occurred. Here is an implementation of readIntegerFile() that throws

an exception object of class invalid_argument if the file cannot be opened and an object of class runtime_exception if the integers cannot be read. Both invalid_argument and runtime_exception are classes defined in the header file <stdexcept> as part of the C++ Standard Library.

#include <fstream> #include <iostream> #include <vector> #include <string> #include <stdexcept>

using namespace std;

void readIntegerFile(const string& fileName, vector<int>& dest)

{

ifstream istr; int temp;

istr.open(fileName.c_str()); if (istr.fail()) {

// We failed to open the file: throw an exception. throw invalid_argument(“”);

}

// Read the integers one by one and add them to the vector. while (istr >> temp) {

dest.push_back(temp);

}

if (istr.eof()) {

//We reached the end-of-file. istr.close();

}else {

//Some other error. Throw an exception.

409

Chapter 15

istr.close();

throw runtime_error(“”);

}

}

There are no public default constructors for invalid_argument and runtime_error, only string constructors.

Now main() can catch both invalid_argument and runtime_error with two catch statements:

int main(int argc, char** argv)

{

//Code omitted try {

readIntegerFile(fileName, myInts); } catch (const invalid_argument& e) {

cerr << “Unable to open file “ << fileName << endl; exit (1);

} catch (const runtime_error& e) {

cerr << “Error reading file “ << fileName << endl; exit (1);

}

//Code omitted

}

If an exception is thrown inside the try block, the compiler will match the type of the exception to the proper catch handler. So, if readIntegerFile() is unable to open the file and throws in invalid_ argument object, it will be caught by the first catch statement. If readIntegerFile() is unable to read the file properly and throws a runtime_error, then the second catch statement will catch the exception.

Matching and Const

The const-ness specified in the type of the exception you want to catch makes no difference for matching purposes. That is, this line matches any exception of type runtime_error.

} catch (const runtime_error& e) {

This line also matches any exception of type runtime_error:

} catch (runtime_error& e) {

You should generally catch exceptions with const to document that you are not modifying them.

Matching Any Exception

You can write a catch line that matches any possible exception with the special syntax shown here:

int main(int argc, char** argv)

{

// Code omitted try {

410

Handling Errors

readIntegerFile(fileName, myInts); } catch (...) {

cerr << “Error reading or opening file “ << fileName << endl; exit (1);

}

// Code omitted

}

The three dots are not a typo. They are a wildcard that match any exception type. When you are calling poorly documented code, this technique can be useful to ensure that you catch all possible exceptions. However, in situations where you have complete information about the set of thrown exceptions, this technique is considered suboptimal because it handles every exception type identically. It’s better to match exception types explicitly and take appropriate, targeted action.

Uncaught Exceptions

If your program throws an exception that is not caught anywhere, the program will terminate. This behavior is not usually what you want. The point of exceptions is to give your program a chance to handle and correct undesirable or unexpected situations. If your program didn’t catch an exception, there was little point in throwing it to begin with.

Catch and handle all possible exceptions thrown in your programs.

Even if you can’t handle a particular exception, you should still write code to catch it and print an appropriate error message before exiting.

It is also possible to change the behavior of your program if there is an uncaught exception. When the program encounters an uncaught exception, it calls the built-in terminate() function, which simply calls abort() from <cstdlib> to kill the program. You can set your own terminate_handler by calling set_terminate() with a pointer to a callback function that takes no arguments and returns no value. terminate(), set_terminate(), and terminate_handler are all declared in the <exception> header. Before you get too excited about this feature, you should know that your callback function must still terminate the program, or else abort() will be called anyway. It can’t just ignore the error. However, you can use it to print a helpful error message before exiting. Here is an example of a main() function that doesn’t catch the exceptions thrown by readIntegerFile(). Instead, it sets the terminate_handler to a callback that prints an error message before exiting:

void myTerminate()

{

cout << “Uncaught exception!\n”; exit(1);

}

int main(int argc, char** argv)

{

vector<int> myInts;

const string fileName = “IntegerFile.txt”;

set_terminate(myTerminate);

readIntegerFile(fileName, myInts);

411