We will implement a program that opens a file (which might fail), and then we'll read an integer out of it (which might fail, too). We do this with activated exceptions and then we see how we can handle those:
- First, we include some headers and declare that we use the std namespace:
#include <iostream>
#include <fstream>
#include <system_error>
#include <cstring>
using namespace std;
- If we want to use stream objects with exceptions, we have to enable them first. In order to get a file stream object to throw an exception if the file we are letting it access does not exist, or if there are parsing errors, we need to set some fail bits in an exception mask. If we do something afterward that fails, it will trigger an exception. By activating failbit and badbit, we enable exceptions for filesystem errors and parsing errors:
int main()
{
ifstream f;
f.exceptions(f.failbit | f.badbit);
- Now we can open a try block and access a file. If opening the file is successful, we try to read an integer from it. Only if both steps succeed, we print the integer:
try {
f.open("non_existant.txt");
int i;
f >> i;
cout << "integer has value: " << i << 'n';
}
- In both the expected possibilities of an error, an instance of std::ios_base::failure is thrown. This object has a what() member function, which ought to explain what triggered the exception. Unfortunately, the standardization of this message was left out, and it does not give too much information. However, we can at least distinguish if there is a filesystem problem (because the file does not exist, for example) or a format parsing problem. The global variable, errno, has been there even before C++ was invented, and it is set to an error value, which we can check now. The strerror function translates from an error number to a human readable string. If errno is 0, there is, at least, no filesystem error:
catch (ios_base::failure& e) {
cerr << "Caught error: ";
if (errno) {
cerr << strerror(errno) << 'n';
} else {
cerr << e.what() << 'n';
}
}
}
- Compiling the program and running it in two different scenarios yields the following output. If the file to be opened does exist but parsing an integer from it was not possible, we get an iostream_category error message:
$ ./readable_error_msg
Caught error: ios_base::clear: unspecified iostream_category error
- If the file does not exist, we will be notified about this with a different message from strerror(errno):
$ ./readable_error_msg
Caught error: No such file or directory