In the example code, function processFile() is supposed to process the specified file. The file name is specified using an object of the standard string class. If the file name is not valid (for example, if it contains illegal characters) or if the file does not exist, processFile() cannot proceed, so it throws an exception. In the case of an invalid file name, processFile() throws an object of class BadFileName; in the case of a nonexistent file, it throws an object of class FileNotFound. Functions isValidFileName() and fileExists() represent routines that determine if a given file name is valid and exists, respectively. As shown below, isValidFileName() always returns true (meaning "yes, the filename is valid") and fileExists() always returns false (meaning "no, the file does not exist"), but in practice these routines would make system calls to determine the proper result. #include <iostream> #include <string> using namespace std; class BadFilename { }; class FileNotFound { }; bool isValidFilename(const string& filename) throw() { // Pretend this checks if filename is a valid filename return true; } bool fileExists(const string& filename) throw() { // Pretend this checks if filename exists as a file return false; } void processFile(const string& filename) throw(BadFilename, FileNotFound) { if (! isValidFilename(filename)) throw BadFilename(); if (! fileExists(filename)) throw FileNotFound(); // the filename is valid and exists; process the file: // ... } void f(const string& filename) throw() { try { processFile(filename); // ... } catch (BadFilename& e) { cout << "Invalid file name: " << filename << "\n"; } catch (FileNotFound& e) { cout << "File not found: " << filename << "\n"; } } try and catch are keywords. The code within the block after the try keyword is executed first. In this case, f() calls processFile(). In a real application, processFile() often succeeds (that is, it often returns normally without throwing an exception), in which case the runtime system continues processing the code in the try block, then skips the catch blocks and proceeds normally. In the case when an exception is thrown, control immediately jumps to the matching catch block. If there is no matching catch block in the caller, control immediately jumps back to the matching catch block in the caller's caller, caller's caller's caller, and so on, until it reaches the catch (...) block in main(), shown below. catch (...) is a special catch-all block: it matches all possible exceptions. int main() { try { f("input-file.txt"); // ... } catch (...) { cout << "Unknown exception!\n"; } } The throw() declarations after the signature of the various functions (e.g., throw() after the signature of function f() and throw(BadFilename, FileNotFound) after the signature of function processFile()) are the function's way of telling callers what it might throw. Functions that say throw() are effectively saying, "This function doesn't throw any exceptions." Functions that say throw(BadFilename, FileNotFound) are effectively saying, "This function might throw a BadFilename object or a FileNotFound object but nothing else." |