Rethrowing Exceptions from catch Blocks
Exception handlers can themselves emit new exceptions or propagate the original ones upward. When an exception is caught, you may need to perform cleanup actions before allowing the error to continue unwinding the call stack. This pattern requires an enclosing try-catch structure to intercept the rethrown exception.
The throw; statement without an operand rethrows the currently handled exception while preserving its original type and value. Alternative, you can throw a completely different object after performing error translation.
This technique proves invaluable when integrating legacy libraries that use primitive error codes. You can intercept these integer values and transform them into descriptive exceptions that match your application's error handling conventions.
#include <iostream>
// Demonstrates nested exception handling and rethrowing
void RethrowDemonstration() {
try {
try {
throw "initial_exception"; // Throw const char* (won't match int handler)
}
catch(int error_code) {
std::cout << "Inner: Caught integer error: " << error_code << std::endl;
throw error_code; // Rethrow as integer
}
catch(...) {
std::cout << "Inner: Caught generic exception" << std::endl;
throw; // Rethrow original const char*
}
}
catch(...) {
std::cout << "Outer: Intercepted rethrown exception" << std::endl;
}
}
// Simulates a third-party library using integer error codes
void LegacyLibraryFunction(int parameter) {
if (parameter < 0) {
throw -1; // Parameter validation failure
}
if (parameter > 100) {
throw -2; // Runtime error
}
if (parameter == 42) {
throw -3; // Timeout condition
}
std::cout << "LegacyLibraryFunction executed successfully" << std::endl;
}
// Translates integer codes into meaningful string exceptions
void ExceptionTranslator(int parameter) {
try {
LegacyLibraryFunction(parameter);
}
catch(int code) {
switch(code) {
case -1:
throw "Invalid parameter: value out of acceptable range";
case -2:
throw "Runtime exception: unrecoverable error occurred";
case -3:
throw "Timeout exception: operation exceeded time limit";
}
}
}
int main() {
RethrowDemonstration();
std::cout << std::endl;
try {
ExceptionTranslator(42);
}
catch(const char* error_message) {
std::cout << "Translated exception: " << error_message << std::endl;
}
return 0;
}
/* Output:
Inner: Caught generic exception
Outer: Intercepted rethrown exception
Translated exception: Timeout exception: operation exceeded time limit
*/
User-Defined Exception Class Hierarchies
Exception objects are not limited to primitive types; user-defined classes serve as excellent carriers for structured error information. The exception matching process follows the same top-down rule as function overload resolution, evaluating catch clauses in declaration order until finding a compatible type.
The assignment compatibility principle remains active during exception handling. A catch block for a base class type will intercept derived class exceptions if listed first, potentially preventing more specific handlers from executing. Therefore, always order catch blocks from most derived to most base types.
When catching class type exceptions, declare the paramter as a const reference to eliminate unnecessary copy construction and improve performance, especially for exceptions with substantial data members.
#include <iostream>
#include <string>
// Base exception class
class GenericError {
public:
virtual ~GenericError() = default;
};
// Derived exception with detailed error information
class ApplicationException : public GenericError {
private:
int error_id_;
std::string description_;
public:
ApplicationException(int identifier, const std::string& details)
: error_id_(identifier), description_(details) {}
int getErrorId() const {
return error_id_;
}
std::string getDescription() const {
return description_;
}
};
// Legacy function throwing primitive error codes
void LegacyLibraryFunction(int parameter) {
if (parameter < 0) throw -1;
if (parameter > 100) throw -2;
if (parameter == 42) throw -3;
std::cout << "Operation completed normally" << std::endl;
}
// Modern wrapper translating codes to exception objects
void ModernExceptionWrapper(int parameter) {
try {
LegacyLibraryFunction(parameter);
}
catch(int code) {
switch(code) {
case -1:
throw ApplicationException(-1, "Parameter validation failed");
case -2:
throw ApplicationException(-2, "Runtime failure detected");
case -3:
throw ApplicationException(-3, "Timeout exceeded");
}
}
}
int main() {
try {
ModernExceptionWrapper(42);
}
catch(const ApplicationException& ex) { // Derived class first
std::cout << "Exception Details:" << std::endl;
std::cout << " Error ID: " << ex.getErrorId() << std::endl;
std::cout << " Description: " << ex.getDescription() << std::endl;
}
catch(const GenericError& ex) { // Base class second
std::cout << "Generic error handler invoked" << std::endl;
}
return 0;
}
/* Output:
Exception Details:
Error ID: -3
Description: Timeout exceeded
*/
Standard Library Exception Hierarchy
The C++ standard library provides a comprehensive exception class hierarchy rooted at the std::exception base class. All standard exceptions inherit from this interface, which defines the virtual what() method for retrieving error descriptions.
The hierarchy splits into two primary categories: logic_error for preventable programming mistakes (like invalid arguments), and runtime_error for unpreventable operational failures (like resource exhaustion). The out_of_range exception, thrown by container operations with invalid indices, descends from logic_error.
Custom template containers should throw standard exceptions to maintain interface consistency with library components. This enables client code to catch std::exception as a universal handler.
Fixed-size array template:
#ifndef FIXED_ARRAY_H
#define FIXED_ARRAY_H
#include <stdexcept>
template <typename ElementType, unsigned int Size>
class FixedArray {
private:
ElementType elements_[Size];
public:
unsigned int length() const { return Size; }
ElementType& operator[](int index) {
if (index >= 0 && index < static_cast<int>(Size)) {
return elements_[index];
}
throw std::out_of_range("FixedArray::operator[]: Index exceeds bounds");
}
ElementType operator[](int index) const {
if (index >= 0 && index < static_cast<int>(Size)) {
return elements_[index];
}
throw std::out_of_range("FixedArray::operator[] const: Index exceeds bounds");
}
};
#endif
Heap-alocated array template:
#ifndef DYNAMIC_ARRAY_H
#define DYNAMIC_ARRAY_H
#include <stdexcept>
template <typename ElementType>
class DynamicArray {
private:
unsigned int capacity_;
ElementType* data_;
DynamicArray(unsigned int size) : capacity_(size), data_(nullptr) {}
bool allocate() {
data_ = new ElementType[capacity_]();
return data_ != nullptr;
}
public:
static DynamicArray<ElementType>* create(unsigned int size) {
DynamicArray<ElementType>* instance = new DynamicArray<ElementType>(size);
if (instance && instance->allocate()) {
return instance;
}
delete instance;
return nullptr;
}
unsigned int length() const { return capacity_; }
ElementType& operator[](int index) {
if (index >= 0 && index < static_cast<int>(capacity_)) {
return data_[index];
}
throw std::out_of_range("DynamicArray::operator[]: Index out of bounds");
}
ElementType operator[](int index) const {
if (index >= 0 && index < static_cast<int>(capacity_)) {
return data_[index];
}
throw std::out_of_range("DynamicArray::operator[] const: Index out of bounds");
}
~DynamicArray() {
delete[] data_;
}
};
#endif
Usage demonstration:
#include <iostream>
#include <memory>
#include "FixedArray.h"
#include "DynamicArray.h"
void TestFixedArray() {
FixedArray<int, 5> array;
for (int i = 0; i < static_cast<int>(array.length()); ++i) {
array[i] = i * 10;
}
// Access beyond bounds throws out_of_range
for (int i = 0; i < 10; ++i) {
std::cout << array[i] << std::endl;
}
}
void TestDynamicArray() {
// Modern smart pointer for automatic cleanup
std::unique_ptr<DynamicArray<double>> array(DynamicArray<double>::create(5));
if (array) {
for (int i = 0; i < static_cast<int>(array->length()); ++i) {
(*array)[i] = i * 1.5;
}
// Access beyond bounds throws out_of_range
for (int i = 0; i < 10; ++i) {
std::cout << (*array)[i] << std::endl;
}
}
}
int main() {
// Test 1: Observe unhandled exception termination
TestFixedArray();
std::cout << std::endl;
TestDynamicArray();
// Test 2: Catch standard exceptions
// try {
// TestFixedArray();
// std::cout << std::endl;
// TestDynamicArray();
// }
// catch (const std::exception& e) {
// std::cout << "Standard exception: " << e.what() << std::endl;
// }
return 0;
}
Key principles demonstrated: catch blocks can rethrow exceptions, custom class hierarchies integrate seamlessly with exception handling mechanics, assignment compatibility governs catch clause matching order, and the standard library provides a robust exception foundation for both library implementers and application developers.