Detailed Explanation of C++ Throw Exception

Detailed Explanation of C++ Throw Exception

In the section on C++ Exception Handling, we talked about the process of C++ exception handling, which is:Throw –> Try –> CatchExceptions must be explicitly thrown to be detected and caught; if not explicitly thrown, even if there is an exception, it cannot be detected.In C++, we use the throw keyword to explicitly throw exceptions, with the usage as follows:throw exceptionData;exceptionData means “exception data”, which can contain any information, completely determined by the programmer. exceptionData can be basic types like int, float, bool, or pointers, arrays, strings, structs, classes, and other aggregate types. Please see the example below:

char str[] = "https://www.sixstaredu.com/";
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++ specifies that once an array is defined, its length cannot change; in other words, the capacity of the array cannot dynamically increase or decrease. Such an array is called a Static Array. Static arrays can sometimes make coding inconvenient, so we can implement a Dynamic Array through a custom Array class. A dynamic array refers to an array whose capacity can increase or decrease at any time during use.The following code 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 message
private:
int m_flag;  // Different flags indicate different errors
int m_len;  // Current length of the array
int m_index;  // Current array index in use
};
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 array
class Array{
public:
Array();
~Array(){ free(m_p); };
public:
int operator[](int i) const;  // Get array element
int push(int ele);  // Insert array element at the end
int pop();  // Delete array element at the end
int length() const{ return m_len; };  // Get array length
private:
int m_len;  // Array length
int m_capacity;  // Current memory can hold how many elements
int *m_p;  // Memory pointer
private:
static const int m_stepSize = 50;  // Step size for 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 ){  // Check for out of bounds
throw OutOfRange(m_len, index);  // Throw exception (create an anonymous object)
}
return *(m_p + index);
}
int Array::push(int ele){
if(m_len >= m_capacity){  // If capacity is insufficient, expand
m_capacity += m_stepSize;
m_p = (int*)realloc( m_p, sizeof(int) * m_capacity );  // Expand
}
*(m_p + m_len) = ele;
m_len++;
return m_len-1;
}
int Array::pop(){
if(m_len == 0){
throw OutOfRange();  // Throw exception (create an anonymous object)
}
m_len--;
return *(m_p + m_len);
}
// Print array elements
void printArray(Array &arr){
int len = arr.length();
// Check if 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 20 elements
try{
for(int i=0; i<20; i++){
nums.pop();
}
}catch(OutOfRange &e){
e.what();
}
printArray(nums);
return 0;
}

Output:0, 1, 2, 3, 4, 5, 6, 7, 8, 9Error: out of range( array length 10, access index 20 )Error: empty array, no elements to pop.Empty array! No elements to print.The Array class implements a dynamic array, where the main idea is to pre-allocate a certain length of memory when creating the object (allocated via malloc()), and when memory is insufficient, expand it (reallocated via realloc()). The Array array can only insert (via push()) or delete (via pop()) elements one by one at the end.We access array elements through the overloaded<span>[ ]</span>operator; if the index is too small or too large, an exception will be thrown (line 53); when throwing the exception, we also record the current length of the array and the index to access.When using pop() to delete array elements, an error will also be thrown if the current array is empty.

Using Throw as Exception Specification

The throw keyword can not only be used in the body of a function to throw exceptions, but can also be used between the function header and body to specify the types of exceptions that the current function can throw, which is called Exception Specification; some tutorials also refer to it as Exception Indicator or Exception List. Please see the example below:double func (char param) throw (int);This statement declares a function named func, which has a return type of double, one char type parameter, and can only throw int type exceptions. If other types of exceptions are thrown, try cannot catch them, and the program will terminate.If a function may throw multiple types of exceptions, they can be separated by commas:double func (char param) throw (int, char, exception);If a function will not throw any exceptions, then<span>( )</span><span> should remain empty:</span><span>double func (char param) throw ();</span><span><span>Thus, the func() function cannot throw any type of exception, and even if it does, try will not detect it.</span></span><h4><span>1) Exception Specification in Virtual Functions</span></h4><span>C++ stipulates that the exception specification of a derived class's virtual function must be as strict as or stricter than that of the base class's virtual function. Only in this way can it be ensured that when calling a derived class's virtual function through a base class pointer (or reference), the exception specification of the base class member function is not violated. Please see the example below:</span><pre><code class="language-c">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); // Correct! Same exception specification
string fun3() throw(string); // Correct! Exception specification is stricter than throw(int,string)
}

2) Exception Specification with Function Definitions and Declarations

C++ specifies that exception specifications must be indicated in both function declarations and definitions, and must be strictly consistent, not stricter or more lenient.Please see the following sets of functions:

// Wrong! Definition has exception specification, declaration does not
void func1();
void func1() throw(int) { }
// Wrong! Exception specifications in definition and declaration are inconsistent
void func2() throw(int);
void func2() throw(int, bool) { }
// Correct! Exception specifications in definition and declaration are strictly consistent
void func3() throw(float, char*);
void func3() throw(float, char*) { }

Please Abandon Exception Specification, Do Not Use It Anymore

The original intention of exception specification is good; it hopes to let programmers see the definition or declaration of a function and immediately know what types of exceptions it will throw, so that programmers can use try-catch to catch them. However, this is sometimes not easy to achieve. For example, the func_outer() function may not raise exceptions, but it calls another function func_inner() which may raise exceptions. Additionally, if you write a function that calls an old library function, it may not raise exceptions, but after the library is updated, this function may raise exceptions.In summary, the original intention of exception specifications is somewhat difficult to implement, so the consensus is that it is better not to use exception specifications.Exception specifications were added in C++98, but later C++11 has abandoned them and does not recommend their use.Additionally, different compilers have different support for exception specifications; please see the code below:

#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;
}

In GCC, this code will crash the program at line 7. Although an exception occurs in the func() function, because throw limits the function to only throw char*, exception type exceptions, the try-catch will not capture the exception and will only hand it over to the system for processing, terminating the program.In Visual C++, the output will be<span>Exception type: int</span>, indicating that the exception was successfully caught. In Visual C++, using exception specifications has no syntax errors but also has no effect; Visual C++ directly ignores the restrictions of exception specifications, and functions can throw any type of exceptions.The editor has organized a set of C Language learning materials. If you need them, please private message @C Language Learning Alliance and reply to receive materials. You are welcome to follow, and I will share related technical articles in a timely manner. Your attention and likes are very important to me, thank you all for moving your fingers to click follow~*Statement:This article is compiled from the internet, and the copyright belongs to the original author. If there are any inaccuracies or infringements, please contact us for deletion or authorization matters.

Leave a Comment