C++ Tidbits – Exceptions

It is the last Wednesday of January and the Twin Cities of Minneapolis and ST. Paul have been experiencing very cold weather. It is being referred to as a polar vortex. Yesterday in the evening news the weatherman mentioned that our temperature was lower than in the South Pole. One way or another, the thermometer currently reads -29F. We expect even lower temperatures tomorrow reaching -35F. Hopefully the forecast will be amended today.

The topic for this post is exceptions in C++. In most languages, including C++ there are different types of exceptions. When you write C++ code your IDE (in this case I am using Visual Studio) may inform you that you are making a typo, not passing the proper arguments, or who knows what. This is typically done with some type of intelligent sensing. If such mechanism does not find the issue then it will be caught by the compiler. One way or another, if the build process succeeds you had generated an executable which in most cases should run.

The next step is to run the code. There is a second class of issues that may arise at such time. For example, the developer might have missed to initialize a value and the program runs into an issue if it attempts a division by zero (0). In other cases the program may prompt users for values and a similar condition may arise. This is just a simple example. When a program expects user input it must always validate it not just to eliminate runtime errors, but to prevent malicious attacks.

Let’s look at a very simple piece of code.

#include "pch.h"

using namespace std;

int main()
{

	// **** division by zero ****
	int numerator	= 12;
	int denominator	= 0;

	// **** missing check ****
	if (denominator == 0) {
		cerr << "main <<< invalid denominator: " << denominator << endl;
//		return -1;
	}

	// **** compute and display the quotient ****
	cout << "main <<< " << numerator / denominator << endl;

	// **** all done ****
	return 0;
}

We will attempt to divide by zero (0) and display the results. That is not possible. As soon as the division is processed the program will throw an exception and will halt execution.

Unhandled exception at 0x00007FF74EAF2BFC in tidbits3.exe: 0xC0000094: Integer division by zero. occurred

The user and the developer do not get a simple indication of what went wrong. Of course, if we would have checked the denominator before the division the program could have prompted the user to enter a different value and retry the calculation. This is not always the case. We might be reading a large amount of values from a file and we do not want to stop if an error is encountered. That said; if each value is checked there will be no exceptions, at least dividing by zero (0).

Let’s look at the following code:

#include "pch.h"

using namespace std;

// **** exception definition ****
const int	DivideByZero = 1;

int main()
{

	// **** division by zero ****
	int numerator	= 12;
	int denominator	= 0;

	try {
		if (denominator == 0) {
			throw DivideByZero;
		} else {
			cout << numerator / denominator << endl;
		}
	}
	catch (int ex) {
		if (ex == DivideByZero)
			cerr << "exception <<< divide by zero denominator: " << denominator << endl;
	}

	// **** all done ****
	return 0;
}

In this example we will check for the zero (0) value and will throw and catch an exception. The idea is to illustrate how exceptions are thrown and caught. If exceptions would work like this, there would be no need for them as illustrated in the previous code. In practice the runtime would throw the exception and our code would catch it and take some appropriate action.

When we run the program we see the following message in the console:

exception <<< divide by zero denominator: 0

We can now define an actual divide by zero exception. This can be done as follows:

#pragma once

#include "pch.h"

using namespace std;

/*
*/
class DivideByZero : public runtime_error
{
public:
	DivideByZero();
};

We could implement it as follows:

#include "pch.h"

using namespace std;

// **** constructor ****
DivideByZero::DivideByZero()
	: runtime_error("division by zero")
{
}

To test our newly created exception we could:

#include "pch.h"

using namespace std;

double quotient(double numerator, double denominator) {
	if (denominator == 0.0) {
		throw DivideByZero();
	}
	else {
		return numerator / denominator;
	}
}

int main()
{

	// **** some variables ****
 	double a, b, c;

	// **** prompt for the values ****
	cout << "main <<< numerator: "; cin >> a;
	cout << "main <<< denominator: "; cin >> b;

	// **** the following may throw an exception ****
	try {
		c = quotient(a, b);
		cout << "main <<< c: " << c << endl;
	}

	catch (DivideByZero &ex) {
		cerr << "main <<< EXCEPTION " << ex.what() << endl;
	}

	catch (...) {
		cerr << "main <<< EXCEPTION" << endl;
	}
   
 	// **** all done ****
	return 0;
}

We have written the quotient() function which takes two arguments and encapsulates a similar approach as we had in the previous example, that is; we first check for a zero (0) in the denominator and if present we throw the exception. If that is not the case, we compute the result and return it to the caller. This is just to simulate what would happen when the runtime throws the exception.

Back in main(), the user is prompted to enter a numerator and a denominator. This time we will try and catch not only that exception, but any other exception that may be thrown. This is done to illustrate how to catch multiple exceptions. It is not needed in this example.

Let’s run the program twice. In the first pass we will not enter a zero (0) for the denominator. In the second pass we will. This is illustrated in the following console capture:

main <<<   numerator: 12
main <<< denominator: 4
main <<<           c: 3

main <<<   numerator: 12
main <<< denominator: 0
main <<< EXCEPTION division by zero

Let’s move on to a file stream exception which is quite common to encounter in actual life. In this case we tell the compiler that we want the runtime to throw an exception if we are not able to find the specified file. The code follows:

#include "pch.h"

using namespace std;

int main()
{

	const string	fileName = "c:\\Temp\\chars.txt";	
//  const string	fileName = "c:\\Temp\\non_existing_file.txt";

	ifstream		file;

	// **** enable file IO exceptions ****
	file.exceptions(ifstream::failbit |
					ifstream::badbit);

	// **** open the specified file ****
	try {

		file.open(fileName);
	}
	catch (ifstream::failure ex) {
		cerr << "main <<< EXCEPTION " << ex.what() << endl;
		cerr << "main <<< EXCEPTION fileName ==>" << fileName << "<== not found" << endl;
		return -1;
	}

	// **** disable file IO exceptions ****
	file.exceptions(0);

	// **** read the contents of the file ****
	while (!file.eof()) {
		cout << (char)file.get();
	}

	// **** close the file ****
	file.close();

	// **** all done ****
	return 0;
}

Note that we have specified a file that exists and another that does not. With the code as is we should be able to read and display the file. This is illustrated by the following console output capture:

Now is the time for all
good people to come to
the aid of their party.

If we make the following modification:

//	const string	fileName = "c:\\Temp\\chars.txt";	
	const string	fileName = "c:\\Temp\\non_existing_file.txt";

We now get the following output:

main <<< EXCEPTION ios_base::failbit set: iostream stream error
main <<< EXCEPTION fileName ==>c:\Temp\non_existing_file.txt<== not found

Note that the exception returned is of little help. We might only use the second message or we could just leave it as is. After opening the file, we disable exceptions.  We then read the text file and close it.

One more thing, since I am using Visual Studio, and am using pre compiled headers, my pch.h file looks as follows:

#ifndef PCH_H
#define PCH_H

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

#include "DivideByZero.h"
#endif //PCH_H

Hope this simple introduction to exceptions in C++ was of help. I strongly suggest to experiment by making changes to the code. That is a simple way to run into new situations which will make you look for answers and in the process learn more about C++ or any other subject you are interested at the time.

John

Please follow me on Twitter:  @john_canessa

Leave a Reply

Your email address will not be published.

This site uses Akismet to reduce spam. Learn how your comment data is processed.