C + + exception handling (try catch throw)

1, Application scenarios and principles of exception handling

1. Scene

  • When doing division, the divisor is 0;
  • The user entered a negative number when entering the age;
  • When using the new operator to dynamically allocate space, the space cannot be allocated due to insufficient space;
  • Subscript out of bounds when accessing array elements;
  • When opening the file for reading, the file does not exist.

2. Principle

  • These exceptions, if not found and handled, are likely to cause the program to crash.
  • The so-called "processing" can give error prompt information, and then let the program continue to execute along a path that will not make an error; You may have to end the program, but do some necessary work before the end, such as writing the data in memory to the file, closing the open file, releasing the dynamically allocated memory space, etc.
  • It may not be appropriate to deal with the exception as soon as it is found, because in some cases, the caller of a function decides how to deal with the exception during the execution of a function. In particular, functions such as library functions that are provided to programmers to call to complete general functions unrelated to specific applications may not meet the needs of the calling program by rashly handling exceptions during execution.
  • In addition, it is not conducive to code maintenance to handle exceptions in different places. In particular, it is unnecessary to write the same processing code for the same exception in different places. If the program can be executed in the same place when various exceptions occur, and the exceptions can be handled centrally in this place, the program will be easier to write and maintain.

**The basic idea is: * * when an exception is found during the execution of function A, it can not be handled, but just "throw an exception" to the caller of A, which is assumed to be function B. Throwing an exception without processing will cause function A to stop immediately. In this case, function B can choose to catch the exception thrown by A for processing or ignore it. If ignored, the exception will be thrown to the caller of B, and so on. If A layer of functions do not handle exceptions, exceptions will eventually be thrown to the outermost main function. The main function should handle exceptions. If the main function does not handle exceptions, the program will immediately abort abnormally.

2, Basic syntax of C + + exception handling

1. C + + handles exceptions through throw statements and try... catch statements. The syntax of throw statement is as follows:

throw  expression;

The statement throws an exception. An exception is an expression whose value can be either a base type or a class.

2. The syntax of the try... catch statement is as follows:

try {
    Statement group
}
catch(Exception type) {
    Exception handling code
}
...
catch(Exception type) {
    Exception handling code
}

catch can have multiple, but at least one.

3. The execution process of the try... catch statement is:

  • Execute the statements in the try block. If no exception is thrown during execution, the statements after the last catch block will be executed after execution, and all statements in the catch block will not be executed;
  • If an exception is thrown during the execution of the try block, immediately jump to the catch block matching the first "exception type" and the thrown exception type for execution (called the exception is "caught" by the catch block), and then jump to the back of the last catch block for execution.

4. Example

#include <iostream>
using namespace std;
int main()
{
    double m ,n;
    cin >> m >> n;
    try {
        cout << "before dividing." << endl;
        if( n == 0)
            throw -1; //Throw int type exception
        else
            cout << m / n << endl;
        cout << "after dividing." << endl;
    }
    catch(double d) {
        cout << "catch(double) " << d <<  endl;
    }
    catch(int e) {
        cout << "catch(int) " << e << endl;
    }
    cout << "finished" << endl;
    return 0;
}
  • The running results of the program are as follows:
    9 6↙
    before dividing.
    1.5
    after dividing.
    finished

    Note when n is not 0, no exception will be thrown in the try block. Therefore, after the normal execution of the try block, the program continues to execute beyond all catch blocks, and none of the catch blocks will be executed.

  • The running results of the program may also be as follows:

    9 0↙
    before dividing.
    catch(int) -1
    finished

    When n is 0, an integer exception is thrown in the try block. After throwing an exception, the try block immediately stops execution. The integer exception will be caught by the first catch block of type matching, that is, enter the catch(int e) block for execution. After the catch block is executed, the program will continue to execute later until it ends normally.

5. Catch statements that can catch any exceptions
If you want to catch any type of exception thrown, you can write the following catch block:

catch(...) {
    ...
}

Such a catch block can catch any exceptions that have not yet been caught. For example, the following procedure:

#include <iostream>
using namespace std;
int main()
{
    double m, n;
    cin >> m >> n;
    try {
        cout << "before dividing." << endl;
        if (n == 0)
            throw - 1;  //Throw integer exception
        else if (m == 0)
            throw - 1.0;  //Throw a double exception
        else
            cout << m / n << endl;
        cout << "after dividing." << endl;
    }
    catch (double d) {
        cout << "catch (double)" << d << endl;
    }
    catch (...) {
        cout << "catch (...)" << endl;
    }
    cout << "finished" << endl;
    return 0;
}

6. Re throw of exception
If an exception thrown during the execution of a function is caught and handled by the catch block in the function, the exception will not be thrown to the caller of the function (also known as the "function of the previous layer"); If the exception is not handled in this function, it will be thrown to the function of the previous layer. For example, the following procedure:

#include <iostream>
#include <string>
using namespace std;
class CException
{
public:
    string msg;
    CException(string s) : msg(s) {}
};
double Devide(double x, double y)
{
    if (y == 0)
        throw CException("devided by zero");
    cout << "in Devide" << endl;
    return x / y;
}
int CountTax(int salary)
{
    try {
        if (salary < 0)
            throw - 1;
        cout << "counting tax" << endl;
    }
    catch (int) {
        cout << "salary < 0" << endl;
    }
    cout << "tax counted" << endl;
    return salary * 0.15;
}
int main()
{
    double f = 1.2;
    try {
        CountTax(-1);
        f = Devide(3, 0);
        cout << "end of try block" << endl;
    }
    catch (CException e) {
        cout << e.msg << endl;
    }
    cout << "f = " << f << endl;
    cout << "finished" << endl;
    return 0;
}

The output results of the program are as follows:
salary < 0
tax counted
devided by zero
f=1.2
finished

After the CountTa function throws an exception, it handles it by itself. This exception will not continue to be thrown to the caller, that is, the main function. Therefore, in the try block of the main function, the statements after CountTax can be executed normally, that is, f = divide (3, 0);.

In line 35, the Devide function throws an exception but does not handle it. The exception will be thrown to the caller of the Devide function, that is, the main function. After this exception is thrown, the provide function ends immediately. Line 14 will not be executed, and the function will not return a value, which can be seen from the fact that the value of f in line 35 will not be modified.

The exception thrown in the provide function is caught by the type matching catch block in the main function. The e object in line 38 is initialized with the copy constructor.

If the exception thrown is a derived class object and the exception type of the catch block is the base class, the two can also match, because the derived class object is also the base class object.

Although the function can also notify the caller of an exception by returning a value or passing a referenced parameter, in this way, it is necessary to judge whether an exception has occurred each time the function is called, which is troublesome when the function is called in multiple places. With the exception handling mechanism, multiple function calls can be written in a try block. Any exception in a call will be captured and processed by the matching catch block, so it is not necessary to judge whether an exception has occurred after each call.

Sometimes, although exceptions are handled in the function, you still want to be able to notify the caller so that the caller knows that an exception has occurred, so that further processing can be carried out. Throwing an exception in the catch block can meet this need. For example:

#include <iostream>
#include <string>
using namespace std;
int CountTax(int salary)
{
    try {
        if( salary < 0 )
            throw string("zero salary");
        cout << "counting tax" << endl;

    }
    catch (string s ) {
        cout << "CountTax error : " << s << endl;
        throw; //Continue to throw the caught exception
    }
    cout << "tax counted" << endl;
    return salary * 0.15;
}
int main()
{
    double f = 1.2;
    try {
        CountTax(-1);
        cout << "end of try block" << endl;
    }
    catch(string s) {
        cout << s << endl;
    }
    cout << "finished" << endl;
    return 0;
}

The output results of the program are as follows:
CountTax error:zero salary
zero salary
finished

throw on line 14; It does not specify what kind of exception is thrown, so what is thrown is the exception caught by the catch block, that is, string("zero salary"). This exception is caught by the catch block in the main function.

6. List of exception declarations for function
In order to enhance the readability and maintainability of the program, so that programmers can see what exceptions the function may throw when using a function, C + + allows to add a list of exceptions it can throw when declaring and defining a function. The specific writing method is as follows:

void func() throw (int, double, A, B, C);
//void func() throw (int, double, A, B, C){...}

The above description indicates that func may throw int, double, and A, B, and C exceptions. The exception declaration list can be written when the function is declared or when the function is defined. If both are written, the two should be consistent.

If the exception declaration list is written as follows:

void func() throw ();

The func function will not throw any exceptions.

A function can throw any type of exception without telling what type of exception it can throw.

If a function throws an exception that is not in its exception declaration list, it will not cause an error during compilation, but the program compiled by Dev C + + will make an error at runtime; The program compiled with Visual Studio 2010 will not make an error, and the exception declaration list will not work.

3, C + + standard exception class

Some classes in the C + + standard library represent exceptions, which are derived from the exception class. Several common exception classes are shown in Figure 1.

bad_typeid,bad_cast,bad_alloc,ios_base::failure,out_of_range is derived from the exception class. When a C + + program encounters some exceptions, even if the throw statement is not written in the program, it will automatically throw the object of the above exception class. These exception classes also have member functions called what, which return exception description information in string form. To use these exception classes, you need to include the header file stdecept.

The following describes the above exception classes. The program output in this section is subject to Visual Studio 2010, and the program output compiled by Dev C + + is different.

1,bad_typeid
When using the typeid operator, this exception is thrown if its operand is a pointer to a polymorphic class and the value of the pointer is NULL.

2,bad_cast
Dynamic in use_ When cast casts a reference from a polymorphic base class object (or reference) to a derived class, this exception will be thrown if the conversion is unsafe. Examples of procedures are as follows:

#include <iostream>
#include <stdexcept>
using namespace std;
class Base
{
    virtual void func() {}
};
class Derived : public Base
{
public:
    void Print() {}
};
void PrintObj(Base & b)
{
    try {
        Derived & rd = dynamic_cast <Derived &>(b);
        //If this conversion is unsafe, bad will be thrown_ Cast exception
        rd.Print();
    }
    catch (bad_cast & e) {
        cerr << e.what() << endl;
    }
}
int main()
{
    Base b;
    PrintObj(b);
    return 0;
}

The output results of the program are as follows:
Bad dynamic_cast!

In the PrintObj function, through dynamic_cast checks whether b refers to a Derived object. If so, it calls its Print member function; If not, an exception will be thrown and Derived::Print will not be called.

3,bad_alloc
This exception is thrown when there is not enough memory for dynamic memory allocation with the new operator. Examples of procedures are as follows:

#include <iostream>
#include <stdexcept>
using namespace std;
int main()
{
    try {
        char * p = new char[0x7fffffff];  //If so much space cannot be allocated, an exception will be thrown
    }
    catch (bad_alloc & e)  {
        cerr << e.what() << endl;
    }
    return 0;
}

The output results of the program are as follows:
bad allocation
ios_base::failure

By default, the I / O stream object does not throw this exception. If some flag bits are set with the exceptions member function of the stream object, this exception will be thrown when an error occurs in opening the file, reading the end of the input stream, etc. It will not be repeated here.

4,out_of_range
When using the at member function of vector or string to access an element according to the subscript, this exception will be thrown if the subscript is out of bounds. For example:

#include <iostream>
#include <stdexcept>
#include <vector>
#include <string>
using namespace std;
int main()
{
    vector<int> v(10);
    try {
        v.at(100) = 100;  //Throw out_of_range exception
    }
    catch (out_of_range & e) {
        cerr << e.what() << endl;
    }
    string s = "hello";
    try {
        char c = s.at(100);  //Throw out_of_range exception
    }
    catch (out_of_range & e) {
        cerr << e.what() << endl;
    }
    return 0;
}

The output results of the program are as follows:
invalid vector subscript
invalid string position

If you replace v.at(100) with v[100] and s.at(100) with s[100], the program will not throw an exception (but may cause the program to crash). Because the at member function detects that the subscript is out of bounds and throws an exception, while the operator [] does not. The advantage of operator [] over at is that it does not need to judge whether the subscript is out of bounds, so the execution speed is faster.

Keywords: C++

Added by deception54 on Thu, 20 Jan 2022 09:39:36 +0200