Syntax Errors
A wrongly typed expression, statement, or construction is a syntax error.
Consider the following two statements:
int arr = {1, 2, 3}; //syntax error, missing []
They are definitions of the same array. The first one is correct. The second one is missing [], and that is a syntax error. A program with a syntax error does not succeed to compile. The compilation fails with an error message indicating the syntax error. Good thing is that a syntax error can always be fixed if the programmer knows what he is doing.
Logic Error
A logic error is an error committed by the programmer when some wrong logical coding is made. It may be the result of ignorance from the programmer to the programming language features or a misunderstanding of what the program should do.
In this situation, the program is compiled successfully. The program works fine, but it produces wrong results. Such an error may be because of making a loop iterate 5 times when it is made to iterate 10 times. It may also be that a loop is unconsciously made to iterate infinitely. The only way to solve this kind of error is to do careful programming and test the program thoroughly before handing it over to the customer.
Runtime Errors
Wrong or exceptional inputs cause runtime errors. In this case, the program was compiled successfully and works well in many situations. In certain situations, the program crashes (and stops).
Imagine that in a program code segment, 8 has to be divided by a number of denominators. So if the numerator 8 is divided by the denominator 4, the answer (quotient) would be 2. However, if the user inputs 0 as the denominator, the program would crash. Division by 0 is not allowed in Mathematics, and it is also not allowed in computing. Division-by-zero should be prevented in programming. Exception handling handles runtime errors, like division-by-zero. The following program shows how to handle the division-by-zero problem without using the exception feature in C++:
using namespace std;
int main()
{
int numerator = 8;
int denominator = 2;
if (denominator != 0 )
{
int result = numerator/denominator;
cout << result << '\n';
}
else
{
cout << "Division by zero is not permitted!" << '\n';
}
return 0;
}
The output is 4. If the denominator was 0, the output would have been:
“Division by zero is not permitted!”
The main code here is an if-else construct. If the denominator is not 0, the division will take place; if it is 0, the division will not take place. An error message will be sent to the user, and the program continues to run without crashing. Runtime errors are usually handled by avoiding the execution of a code segment and sending an error message to the user.
The exception feature in C++ uses a try-block for the if-block and a catch-block for the else-block to handle the error, just as follows:
using namespace std;
int main()
{
int numerator = 8;
int denominator = 2;
try
{
if (denominator != 0 )
{
int result = numerator/denominator;
cout << result << '\n';
}
else
{
throw 0;
}
}
catch (int err)
{
if (err == 0)
cout << "Division by zero is not permitted!" << '\n';
}
return 0;
}
Note that the try header does not have an argument. Also note that the catch-block, which is like a function definition, has a parameter. The type of parameter must be the same as the operand (argument) of the throw-expression. The throw-expression is in the try-block. It throws an argument of the programmer’s choice that is related to the error, and the catch-block catches it. In that way, the code in the try-block is not executed. Then, the catch-block displays the error message.
This article explains exception handling in C++. Basic knowledge in C++ is a prerequisite for the reader to understand this article.
Article Content:
- Function Throwing an Exception
- More than One Catch-Blocks for One Try-block
- Nested try/catch Blocks
- noexcept-specifier
- The Special std::terminate() Function
- Conclusion
Function Throwing an Exception:
A function can also throw an exception just like what the try-block does. The throwing takes place within the definition of the function. The following program illustrates this:
using namespace std;
void fn(const char* str)
{
if (islower(str[0]))
throw 'l';
}
int main()
{
try
{
fn("smith");
}
catch (char ch)
{
if (ch == 'l')
cout << "Person's name cannot begin in lowercase!" << '\n';
}
return 0;
}
Notice that this time, the try block has just the function call. It is the function called that has the throw operation. The catch block catches the exception, and the output is:
“Person's name cannot begin in lowercase!”
This time, the type thrown and caught is a char.
More than One Catch-Blocks for One Try-block:
There can be more than one catch-block for one try-block. Imagine the situation where an input can be any of the characters of the keyboard, but not a digit and not an alphabet. In this case, there must be two catch-blocks: one for an integer to check the digit and one for a char to check the alphabet. The following code illustrates this:
There is no output. If the value of input were a digit, e.g., ‘1’, the output would have been:
"Digit input is forbidden!"
If the value of input were an alphabet, e.g., ‘a’, the output would have been:
"Character input is forbidden!"
Note that in the parameter list of the two catch-blocks, there is no identifier name. Also note that in the definition of the two catch-blocks, the particular arguments thrown have not been verified whether their values are exact or not.
What matters for a catch is the type; a catch must match the type of operand thrown. The particular value of the argument (operand) thrown can be used for further verification if needed.
More than one Handler for the Same Type
It is possible to have two handlers of the same type. When an exception is thrown, control is transferred to the nearest handler with a matching type. The following program illustrates this:
using namespace std;
char input = '1';
int main()
{
try
{
if (isdigit(input))
throw 10;
}
catch (int)
{
cout << "Digit input is forbidden!" << '\n';
}
catch (int)
{
cout << "Not allowed at all: digit input!" << '\n';
}
return 0;
}
The output is:
Nested try/catch Blocks:
try/catch blocks can be nested. The above program for the input of non-alphanumeric characters from the keyboard is repeated here, but with the alphabetic error code nested:
The error alphabetic try/catch-block is nested in the try-block of the digit code. The operation of this program and the previous operation from which it is copied are the same.
noexcept-specifier
Consider the following function:
Notice the specifier ‘noexcept’ just after the right parenthesis of the function parameter list. This means that the function should not throw an exception. If the function throws an exception, as in this case, it will compile with a warning message but will not run. An attempt to run the program will call the special function std::terminate(), which should halt the program gracefully instead of just allowing it to literally crash.
The noexcept specifier is in different forms. These are as follows:
type func() noexcept(true); : allows a throw expression
type func() throw(); : does not allow a throw expression
type func() noexcept(false); : allows a throw expression, which is optional
type func(); : allows a throw expression, which is optional
true or false in the parentheses can be replaced by an expression that results in true or false.
The Special std::terminate() Function:
If an exception cannot be handled, it should be re-thrown. In this case, the thrown expression may or may not have an operand. The special function std::terminate() will be called at runtime, which should halt the program gracefully instead of just allowing it to literally crash.
Type, compile, and run the following program:
using namespace std;
char input = '1';
int main()
{
try
{
if (isdigit(input))
throw 10;
}
catch (int)
{
throw;
}
return 0;
}
After a successful compilation, the program terminated without running, and the error message from the author’s computer is:
“terminate called after throwing an instance of ‘int’
Aborted (core dumped)”
Conclusion:
The exception feature in C++ prevents a code segment from executing based on some kind of input. The program continues to execute as necessary. The exception (error prevention) construct consists of a try-block and a catch-block. The try-block has the code segment of interest, which may be bypassed, depending on some input condition. The try-block has the throw expression, which throws an operand. This operand is also called the exception. If the operand type and the type for the parameter of the catch block are the same, then the exception is caught (handled). If the exception is not caught, the program will be terminated, but still, be safe since the code segment that was to be executed to give the wrong result has not been executed. Typical exception handling means bypassing the code segment and sending an error message to the user. The code segment is executed for normal input but bypassed for wrong inputs.