Detailed explanation of C++ throw

Detailed explanation of C++ throw

In the previous section, we talked about the process of C + + exception handling, specifically:

Throw – > Try - > Catch

Exceptions must be explicitly thrown before they can be detected and caught; If there is no explicit throw, even if there is an exception, it cannot be detected.

In C + +, we use the throw keyword to explicitly throw an exception. Its usage is:

throw exceptionData;

exceptionData means "abnormal data". It can contain any information, which is completely up to the programmer to decide. exceptionData can be basic types such as int, float and bool, or aggregate types such as pointer, array, string, structure and class. Please see the following examples:

char str[] = "http://c.biancheng.net";
char *pstr = str;
class Base{};
Base obj;
throw 100;  //int type
throw str;  //Array type
throw pstr;  //Pointer type
throw obj;  //object type

An example of a dynamic array

C/C + + stipulates that once the Array is defined, its length cannot be changed; In other words, Array capacity cannot be dynamically increased or decreased. Such an Array is called a Static array. Static arrays sometimes make coding code inconvenient. We can implement dynamic arrays through custom Array classes. The so-called Dynamic array means that the capacity of the Array can be increased or decreased at any time in the process of use.

Although the following code is a little long, it is a typical scenario of using exceptions. Please read it patiently.

#include <iostream>
#include <cstdlib>
using namespace std;
//Custom exception type
class OutOfRange{
public:
    OutOfRange(): m_flag(1){ };
    OutOfRange(int len, int index): m_len(len), m_index(index), m_flag(2){ }
public:
    void what() const;  //Get specific error information
private:
    int m_flag;  //Different flag s represent different errors
    int m_len;  //The length of the current array
    int m_index;  //Currently used array subscript
};
void OutOfRange::what() const {
    if(m_flag == 1){
        cout<<"Error: empty array, no elements to pop."<<endl;
    }else if(m_flag == 2){
        cout<<"Error: out of range( array length "<<m_len<<", access index "<<m_index<<" )"<<endl;
    }else{
        cout<<"Unknown exception."<<endl;
    }
}
//Implement dynamic array
class Array{
public:
    Array();
    ~Array(){ free(m_p); };
public:
    int operator[](int i) const;  //Get array elements
    int push(int ele);  //Insert array elements at the end
    int pop();  //Delete array elements at the end
    int length() const{ return m_len; };  //Get array length
private:
    int m_len;  //Array length
    int m_capacity;  //How many elements can the current memory hold
    int *m_p;  //Memory pointer
private:
    static const int m_stepSize = 50;  //Step size of each expansion
};
Array::Array(){
    m_p = (int*)malloc( sizeof(int) * m_stepSize );
    m_capacity = m_stepSize;
    m_len = 0;
}
int Array::operator[](int index) const {
    if( index<0 || index>=m_len ){  //Judge whether the boundary is crossed
        throw OutOfRange(m_len, index);  //Throw an exception (create an anonymous object)
    }
    return *(m_p + index);
}
int Array::push(int ele){
    if(m_len >= m_capacity){  //If the capacity is insufficient, expand it
        m_capacity += m_stepSize;
        m_p = (int*)realloc( m_p, sizeof(int) * m_capacity );  //Capacity expansion
    }
    *(m_p + m_len) = ele;
    m_len++;
    return m_len-1;
}
int Array::pop(){
    if(m_len == 0){
         throw OutOfRange();  //Throw an exception (create an anonymous object)
    }
    m_len--;
    return *(m_p + m_len);
}
//Print array elements
void printArray(Array &arr){
    int len = arr.length();
    //Determine whether the array is empty
    if(len == 0){
        cout<<"Empty array! No elements to print."<<endl;
        return;
    }
    for(int i=0; i<len; i++){
        if(i == len-1){
            cout<<arr[i]<<endl;
        }else{
            cout<<arr[i]<<", ";
        }
    }
}
int main(){
    Array nums;
    //Add ten elements to the array
    for(int i=0; i<10; i++){
        nums.push(i);
    }
    printArray(nums);
    //Try to access the 20th element
    try{
        cout<<nums[20]<<endl;
    }catch(OutOfRange &e){
        e.what();
    }
    //Try to pop up 20 elements
    try{
        for(int i=0; i<20; i++){
            nums.pop();
        }
    }catch(OutOfRange &e){
        e.what();
    }
    printArray(nums);
    return 0;
}

Operation results:

0, 1, 2, 3, 4, 5, 6, 7, 8, 9
Error: out of range( array length 10, access index 20 )
Error: empty array, no elements to pop.
Empty array! No elements to print.

Array class implements dynamic array. Its main idea is to allocate a certain length of memory in advance when creating objects (allocated through malloc()), and then expand memory when memory is not enough (realloc()). Array arrays can only insert (push() insert) or delete (pop() delete) elements one by one at the end.

We use the overloaded [] operator to access array elements. If the subscript is too small or too large, an exception will be thrown (line 53); While throwing an exception, we also record the length of the current array and the subscript to be accessed.

When using pop() to delete array elements, an error will also be thrown if the current array is empty.

throw as exception specification

Throw keyword can be used not only in the function body to throw an exception, but also between the function header and the function body to indicate the type of exception that the current function can throw. This is called Exception specification. Some tutorials are also called exception indicator or exception list. Take the following example:
double func (char param) throw (int);

This statement declares a function named func, whose return value type is double, has a parameter of type char, and can only throw exceptions of type int. If other types of exceptions are thrown, try cannot catch them and can only terminate the program.

If the function throws multiple types of exceptions, it can be separated by commas:

double func (char param) throw (int, char, exception);

If the function does not throw any exceptions, nothing is written in ():

double func (char param) throw ();

In this way, func() function cannot throw any type of exception. Even if it is thrown, try cannot detect it.

1) Exception specification in virtual function

C + + stipulates that the exception specification of derived class virtual functions must be as strict as or more strict than that of base class virtual functions. Only in this way, when the derived class virtual function is called through the base class pointer (or reference), can we ensure that the exception specification of the base class member function is not violated. Take the following example:

class Base{
public:
    virtual int fun1(int) throw();
    virtual int fun2(int) throw(int);
    virtual string fun3() throw(int, string);
};
class Derived:public Base{
public:
    int fun1(int) throw(int);   //wrong! Exception specification is not as strict as throw()
    int fun2(int) throw(int);   //yes! Have the same exception specification
    string fun3() throw(string);  //yes! Exception specification is stricter than throw(int,string)
}

2) Exception specification and function definition and function declaration

C + + stipulates that the exception specification must be specified in the function declaration and function definition at the same time, and must be strictly consistent, not more strict or more relaxed.

See the following groups of functions:

//wrong! There is an exception specification in the definition, but not in the declaration
void func1();
void func1() throw(int) { }
//wrong! The exception specification in the definition and declaration is inconsistent
void func2() throw(int);
void func2() throw(int, bool) { }
//yes! The exception specifications in the definition and declaration are strictly consistent
void func3() throw(float, char*);
void func3() throw(float, char*) { }

Please discard the exception specification and do not use it again

The original intention of exception specification is good. It hopes to let programmers know what type of exception the function will throw immediately after seeing the definition or declaration of the function, so that programmers can use try catch to catch it. If there is no exception specification, programmers must read the function source code to know what exceptions the function will throw.

But sometimes it's not easy. For example, func_ The outer() function may not throw an exception, but it calls another function func_inner(), this function may throw an exception. For another example, the function you write calls the old library function, which will not throw an exception at this time, but this function throws an exception after the library is updated. In short, the original intention of exception specification is a little difficult to implement, so the consensus is that it is best not to use exception specification.

Exception specification is a new function of C++98, but later C++11 has abandoned it and is no longer recommended.

In addition, different compilers support exception specifications. Please see the following code:

#include <iostream>
#include <string>
#include <exception>
using namespace std;
void func()throw(char*, exception){
    throw 100;
    cout<<"[1]This statement will not be executed."<<endl;
}
int main(){
    try{
        func();
    }catch(int){
        cout<<"Exception type: int"<<endl;
    }
    return 0;
}

Under GCC, the program will crash when this code runs to line 7. Although an exception occurred in the func() function, the throw limits that the function can only throw exceptions of char * and exception types. Therefore, try catch will not catch the exception and can only be handed over to the system for processing to terminate the program.

In Visual C + +, the output result is Exception type: int, which indicates that the exception was successfully caught. Although using exception specification in Visual C + + has no syntax error, it has no effect. Visual C + + will directly ignore the limitations of exception specification, and functions can throw any type of exception.

Keywords: C++ Back-end

Added by metrostars on Sun, 13 Feb 2022 03:15:27 +0200