800-C++ throw (throw exception) detailed explanation

Keywords: C++ Back-end

Detailed explanation of C++ throw

Throw( Throw)--> Detection( Try) --> Capture( Catch)

Exceptions must be explicitly thrown before they can be detected and caught; if they are not explicitly thrown, even exceptions cannot be detected.

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

throw exceptionData;

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

char str[] = "hello world 666";
char *pstr = str;
class Base{};
Base obj;
throw 100;//int type
throw str;//Array type
throw pstr;//Pointer type
throw obj;//object type

Examples of dynamic arrays

C/C + + stipulates that once an Array is defined, its length cannot be changed; in other words, the Array capacity cannot be dynamically increased or reduced. Such an Array is called a Static array. Static arrays sometimes inconvenience coding code. We can implement dynamic arrays through a custom Array class 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.

#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;
    }
}
//Implementing dynamic arrays
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 it is out of bounds
        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;
}


The Array class implements a dynamic Array. Its main idea is to pre allocate a certain length of memory (allocated through malloc()) when creating objects, and then expand the memory (realloc()) when the memory is insufficient. The Array can only insert (push() insert) or delete (pop() delete) elements one by one at the tail.

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 code); while throwing the 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 exceptions, but also between the function header and the function body to indicate the types of exceptions that can be thrown by the current function. This is called Exception specification. Some tutorials are also called exception indicators or exception lists. See the following examples:

double func (char param) throw (int);

This statement declares a function named func. Its return value type is double, has a parameter of char type, and can only throw exceptions of int type. If other types of exceptions are thrown, try will not be able to catch 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 functions
C + + stipulates that the exception specification of the derived class virtual function must be as strict as or more strict than that of the base class virtual function. Only in this way can we ensure that the exception specification of the base class member function is not violated when the derived class virtual function is called through the base class pointer (or reference). See 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 sets of functions:

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

Be careful when using exception specifications

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

However, this is sometimes not easy to do. For example, the func_outer() function may not throw an exception, but it calls another function func_inner() , this function may throw exceptions. For another example, the function you write calls the old library function, which will not throw exceptions at this time, but this function throws exceptions after the library is updated. In short, the original intention of the exception specification is a little difficult to implement, so everyone agreed that it is best not to use the 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 occurs in the func() function, because throw limits the function to throw only exceptions of char * and exception types, 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 the function can throw any type of exception.

Posted by Mucello on Fri, 12 Nov 2021 02:04:14 -0800