C++ Exceptions
Exception Handling is one of the reasons C++ is superior to C. Runtime abnormalities or unusual circumstances that a program runs across are known as exceptions. Two categories of exceptions exist: Asynchronous (i.e., exceptions outside the program's control, including disc failure, keyboard interrupts, etc.) and Synchronous (i.e., For this reason, C++ offers the following specific keywords:
- Try this: It represents a code block with the ability to throw exceptions.
- Catch: Denotes a sequence of instructions that is carried out in response to a specific exception being thrown.
- Throw: A command for tossing an exception. Used to list exceptions that a function raises but is unable to handle as well.
Why Handle Exceptions?
The primary benefits of exception handling over conventional error handling are as follows:
- Error Handling Code Is Separated from Normal Code: Conventional error handling codes always use if-else statements to manage errors. The code is meant to handle errors, and these conditions get entangled with the regular flow. As a result, the code takes more work to read and update. The error-handling code is separated from the main flow using try/catch blocks.
- Only the exceptions that Functions and/or Methods decide to handle: Although a function may choose to handle some exceptions, it is capable of throwing numerous. The caller can deal with the other exceptions, which are thrown but not captured. The caller of the caller will handle the exceptions if the caller decides not to catch them.
The throw keyword in C++ allows a function to declare the exceptions it will throw. This function's caller needs to catch the exception or specify it again in order to manage it.
3. Classification of Error Types: Objects and basic types can both be thrown as exceptions in C++. It is possible to organize exceptions into namespaces or classes, construct a hierarchy of exception objects, and classify exceptions based on their types.
Exceptions in C++:
A variety of problems can arise when running C++ code, including mistakes in the code created by the programmer, errors brought on by incorrect input, and other unforeseen circumstances.
Normally, C++ will halt and provide an error message when an error occurs. This is known as "C++ will throw an exception" (error) in technical terms.
C++ attempt to catch:
In C++, there are three keywords for managing exceptions: try, throw, and catch.
You can specify a section of code to be tested for errors as it is being performed using the try statement.
When an issue is found, the throw keyword raises an exception, allowing us to write our unique error.
You can specify a block of code to be run in the event that an error occurs in the try block by using the catch statement.
The terms "try and catch" are paired:
We test some code using the try block: The catch block will handle an exception that is thrown if the value of the "age" variable is less than 18.
We detect errors and take appropriate action in the catch block. The catch statement requires one input. Therefore, we may send "int myInt" as the parameter to the catch statement, where the variable "myInt" is used to output the value of age if the value of age is 16, and that is the reason we are throwing an exception of type int in the try block (age).
If there is no mistake (for example, if the age is 21 rather than 16), it will be more than 18, and there is no catch block.
Managing Exceptions in C++
- Here is a basic example of how to handle exceptions in C++. The program's output describes how try/catch blocks are executed.
Example:
#include <iostream>
using namespace std;
int main()
{
int y = -1;
// Some code
cout << "Before try block in code \n";
try {
cout << "Inside try block \n";
if (y < 0)
{
throw x;
cout << "After throwing a block in code (No execution) \n";
}
}
catch (int y ) {
cout << "Exception found \n";
}
cout << "After catch block (Will be executed) \n";
return 0;
}
Output:
Before try block
Inside try block
Exception found
After catch block (Will be executed)
2. Any exception can be caught using a unique catch block known as the "catch all" block, which is represented by the notation catch(...). For instance, when an int is thrown as an exception in the program below, the catch(...) block will be run since there isn't a catch block for ints.
Code:
#include <iostream>
using namespace std;
int main()
{
try {
throw 8;
}
catch (char *excp) {
cout << "Caught the Exception " << excp;
}
catch (...) {
cout << " Exception\n";
}
return 0;
}
Output:
Exception
3. Primitive types do not undergo implicit type conversion. For instance, 'b' is not implicitly transformed to int in the program that follows.
Code:
#include <iostream>
using namespace std;
int main()
{
try {
throw 'b';
}
catch (int x) {
cout << "Caught Exception " << x;
}
catch (...) {
cout << " Exception\n";
}
return 0;
}
4. The software stops erroneously if an exception is thrown and not captured somewhere. A char is thrown, for instance, in the program below, but no catch block is there to catch it.
Code:
#include <iostream>
using namespace std;
int main()
{
try {
throw 'b';
}
catch (int x) {
cout << "Caught ";
}
return 0;
}
Output:
terminate called after throwing an instance of 'char'
This application has requested the Runtime to terminate it unusually. Please get in touch with the application's support team for more information.
5. An exception from a derived class needs to be handled before one from a base class. For further information, view this.
6. The foundation class for all standard exceptions in the C++ library is the standard exception class, the same as in Java. This class is the base class from which all objects thrown by standard library components are derived. Therefore, by capturing this type, all standard exceptions may be captured.
7. All exceptions in C++ are unchecked, meaning that the compiler does not verify whether an exception is caught or not, in contrast to Java (See here for more). Therefore, only some uncaught exceptions have to be listed in a function declaration. Even if doing so is advised as a best practice. The program that follows, for instance, compiles OK, but ideally, the fun() signature should enumerate the untested exclusions.
Code:
#include <iostream>
using namespace std;
// This function signature is fine by the compiler, but not recommended.
// Ideally, the function should specify all uncaught exceptions and the function
// signature should be "void fun(int *ptr, int x) throw (int *, int)"
void fun(int *ptr, int x)
{
if (ptr == NULL)
throw ptr;
if (x == 0)
throw x;
/* Some functionality */
}
int main()
{
try {
fun(NULL, 0);
}
catch(...) {
cout << "Found exception from fun()";
}
return 0;
}
Output:
Found exception from fun()
Another method of writing the above function:
Code:
#include <iostream>
using namespace std;
// Here, we specify the exceptions that this function
// throws.
void fun(int *ptr, int x) throw (int *, int) // Dynamic Exception specification
{
if (ptr == NULL)
throw ptr;
if (x == 0)
throw x;
/* Some functionality */
}
int main()
{
try {
fun(NULL, 0);
}
catch(...) {
cout << "found exception from fun()";
}
return 0;
}
Please take note that as of C++11, using the Dynamic Exception Specification has been discontinued. It could arbitrarily terminate your software, which could be one of the causes. This may occur if you throw an exception of a different kind than those specified in the dynamic exception definition. In such case, your program calls (indirectly) terminate(), which calls abort() by default. Thus, it will abort itself.
Output:
Found exception from fun()
8. Try/catch blocks in C++ to allow for nesting. Additionally, "throw;" can be used to rethrow an exception.
Code:
#include <iostream>
using namespace std;
int main()
{
try {
try {
throw 20;
}
catch (int n) {
cout << "Partially handling ";
throw; // Re-throwing an exception
}
}
catch (int n) {
cout << "Remaining handling";
}
return 0;
}
Output:
Partially handling Reaming handling
The same "throw;" function may use syntax to toss itself again. A function can take care of some of the work and pass the rest to the caller.
9. All objects generated within the enclosing try block are destroyed upon the throwing of an exception prior to the control being passed to the catch block.
Code:
#include <iostream>
using namespace std;
class Test {
public:
Test() { cout << " Test Constructor " << endl; }
~Test() { cout << “ Test Destructor " << endl; }
};
int main()
{
try {
Test t1;
throw 10;
}
catch (int i) {
cout << "Found" << i << endl;
}
}
Output:
Test Constructor
Test Destructor
Found
10. Rethrowung Exceptions:
Rethrowing exceptions is a mechanism in C++ where an exception that is stuck in a seize block can be rethrown to allow it to propagate in addition up the call stack. This can be useful in eventualities where you need to capture an exception at one degree of your program and carry out some managing or logging, after which let the exception hold propagate up for further dealing with at a better degree.
Code:
#include <iostream>
#include <stdexcept>
void foo() {
try {
// Some code that might throw an exception
throw std::runtime_error("Exception in foo()");
} catch (const std::exception& e) {
// Handle the exception at this level
std::cerr << "Caught exception in foo(): " << e.what() << std::endl;
// Rethrow the exception to let it propagate further
throw; // This rethrows the caught exception
}
}
int main() {
try {
foo();
} catch (const std::exception& e) {
std::cerr << "Caught exception in main(): " << e.what() << std::endl;
}
return 0;
}
In this situation:
- The foo feature incorporates code that can throw an exception (std::runtime_error in this case).
- Inside the seize block in foo, the stuck exception is logged or dealt with in a few ways.
- The throw; declaration is used to rethrow the stuck exception.
When an exception is rethrown, it continues to propagate up the call stack. In this case, the rethrown exception is stuck inside the catch block inside the principal characteristic.
Rethrowing exceptions is beneficial when you want to deal with an exception at a particular level of your application; nevertheless, you need general exception management to occur at a higher stage. It lets in for a separation of worries, where exceptional parts of this system can handle exceptions specific to their context.
11. Cleanup with ultimately (using RAII):
C++ no longer has an ultimate block like a few other languages, but you may reap similar functionality using RAII (Resource Acquisition Is Initialization) standards. For instance, the use of smart recommendations and other useful resource control techniques.
Code:
#include <iostream>
#include <memory>
struct Cleanup {
Cleanup() { std::cout << "Acquiring resources..." << std::endl; }
~Cleanup() { std::cout << "Releasing resources..." << std::endl; }
};
void exampleFunction() {
Cleanup cleanup; // RAII: Resources acquired in the constructor, released in the destructor
// ... rest of the function
}
int main() {
try {
exampleFunction();
} catch (const std::exception& e) {
std::cerr << "Caught exception: " << e.what() << std::endl;
}
return 0;
}
In this situation, the Cleanup struct ensures that resources are released while it is going out of scope, whether or not the feature completes commonly or an exception is thrown.
12. Avoiding Resource Leaks:
Be cautious when the usage of dynamic reminiscence allocation inside try blocks. If an exception happens and memory isn't well cleaned up, it can result in useful resource leaks. Smart pointers and different RAII-based totally lessons can assist save you from such leaks.
13.Don't Use Exceptions for Control Flow:
Exceptions are meant for incredible conditions, not for regularly manipulated glide. Overusing exceptions for flow management can make the code less readable and might have overall performance implications.
Conclusion
In the end, exceptions in C++ provide a powerful mechanism for handling errors and tremendous conditions in an application. They permit you to separate mistakes handling code from the regular waft of your program, enhancing code readability and maintainability. Here are a few key points to remember about C++ exceptions:
1. Throwing Exceptions:
- Use the throw keyword to raise an exception when an amazing scenario takes place.
- Exceptions may be of any kind. However, it is commonplace to use trendy exception instructions or custom exception lessons derived from std::exception.
2. Catching Exceptions:
- Employ try-catch blocks to handle exceptions.
- Catch specific exception sorts to offer targeted error dealing with.
- Use the catch (...) block for a time-honored trap-all case.