Part 11: Error handling
Errors in programming are the standard. However careful we may be there comes a time that our code encounters situations it cannot handle. Sometimes we are dealing with numbers hat lead to numerical errors and other times the program comes across a file it cannot read. All these situations may lead to a crash. C++ has built-in tools that can help us catch these errors and handle them gracefully.
Writing robust code
The first step to error handling is adopting techniques that make our code robust and leave very few chances to errors. Here are some good programming habits.
Reuse code
Always break your code down to small and simple functions. Test them extensively and reuse them in your projects. They can become the building blocks of a stable and robust program. Here is a simple function that calculates the value of a polynomial:
double polynomial(const std::vector<double>& coeffs, double x) {
size_t p = coeffs.size() - 1; // the maximum power of x
double val = 0;
for (auto it = coeffs.begin(); it != coeffs.end(); ++it) {
val += (*it) * pow(x, p);
--p; // as we go the power of x decreases
}
return val;
}
This could be a part of a mathematics library in a physics simulation program or a game. Extensive use and testing will guarantee that it is a safe piece of code to use.
Use reliable external libraries
As consequence of the previous is the selection of reliable external libraries. The first library we rely on is the C++ Standard Library. For 3D graphics we rely on OpenGL and its successor Vulkan. The dedicated work behind the creation of these libraries and their use by others like you is guarantee that these libraries have been extensively tested and are error free.
Validate your data
Validate your data as early as possible in your code. Make sue that there is nothing there that could lead to a crash. Validating at each step is not so easy and it leads to very slow code. Making sure that our data are clear of unwanted values helps us write simple and efficient code being sure that no error will occur.
Test your code
Design and develop test mechanisms in your code that will run and test it under the harder conditions automatically and report any errors that might come up. Run these tests systematically, every night if possible, to make sure that your code responds as expected under any conditions.
Exceptions
Exceptions in C++, provide a robust way to handle errors that arise during program execution. When a division by zero occurs or a file fails to open the system raises an exception. C++ has a built-in mechanism that can catch exceptions. C++ Standard Library creates a programmer friendly interface to this mechanism.
try / catch
try / catch is the building block of exception handling in C++. The basic syntax is like this:
try {
code that might cause problems
}
catch (...) { // catch all exceptions
code to perform clean-up if an error occurs
}
We place our code that might run into problems in the try block. Should anything go wrong the code automatically resumes in the catch block. There we usually put code to perform any cleanup before we return from our function.
Here is a simple example:
void exception_s()
{
int i = -1;
try {
char* buf = new char[i]; // bad allocation exception
std::cout << "this should not be seen\n";
}
catch (...) { // catch all exceptions
std::cout << "simple_exception catches exception" << "\n";
}
std::cout << "exception_s terminates gracefully!\n";
}
Going one step further we can get some information about the error that occurred:
void exception2_s()
{
int i = -1;
try {
char* buf = new char[i]; // bad allocation exception
std::cout << "this should not be seen\n";
}
catch (const std::exception& e) {
std::cout << "exception2_s catches exception:" << e.what() << "\n";
}
std::cout << "exception2_s terminates gracefully!\n";
}
Using the generic exception, we catch everything (remember polymorphism and virtual functions) and we can get specific information about the error.
Catching specific exceptions
We can have multiple catch blocks in exception handling, so that we can take different actions depending on the error. In the following example the function generates two different errors and uses different catch blocks to handle them individually:
void throw_some_exception(){
throw std::bad_array_new_length(); // raise a random exception
}
void exception3_s(int ec){
int i = -1;
try {
if (ec == 1)
throw_some_exception();
else
char* buf = new char[i]; // bad allocation exception
std::cout << "this should not be seen\n";
}
catch (const std::bad_array_new_length& e) {
// the code resumes here after the bad_array_new_length exception
std::cout << "exception3_s specific exception:" << e.what() << "\n";
}
catch (const std::exception& e) {
// the code resumes here after any other exception
std::cout << "exception3_s general exception:" << e.what() << "\n";
}
std::cout << "exception3_s terminates gracefully!\n";
}
Throwing custom exceptions
We can create and throw custom exceptions in programs too. Using exceptions to break from situations that may lead to crashes is a good but very difficult technique. It requires very good knowledge of the overall architecture of the application, careful planning and above all sticking to the plan or things will get out of hand very soon with unpredictable results.
class custom_exception : public std::exception {
std::string what_s;
public:
custom_exception(const char* msg = "this is a custom exception") :std::exception(), what_s(msg){
}
virtual const char* what() const throw() {
return what_s.c_str();
}
};
void custom_s(int ex) {
try {
if (ex == 1)
throw custom_exception();
else
throw std::bad_array_new_length(); // raise a random exception
}
catch (const custom_exception& e) {
std::cout << "custom_s specific exception:" << e.what() << "\n";
}
catch (const std::exception& e) {
// the code resumes here after any other exception
std::cout << "custom_s general exception:" << e.what() << "\n";
}
std::cout << "custom_s terminates gracefully!\n";
}
Summary
Here we tried to approach the topic of writing stable and robust applications. The main topics were:
- Habits that will lead to better code
- Using exceptions to safeguard our code
- Developing and throwing our custom exceptions