Skip to main content

C++ Throw

Introduction

Exception handling is a critical part of writing robust and reliable C++ programs. The throw keyword is one of the three pillars of C++ exception handling, alongside try and catch. It allows you to signal that an exceptional situation has occurred that the normal flow of the program cannot handle.

In this tutorial, we'll explore:

  • What the throw keyword does
  • How to use it properly
  • When you should throw exceptions
  • Best practices for throwing exceptions

Understanding the Throw Keyword

The throw keyword in C++ is used to generate (or "raise") an exception. When a throw statement is executed, program control is transferred to the nearest appropriate catch block that can handle the exception.

Basic Syntax

The basic syntax of the throw statement is:

cpp
throw expression;

Where expression can be any type of value or object that represents the error that occurred.

Throwing Basic Types

You can throw values of any type in C++, including primitive types like integers, floating-point numbers, and characters.

Example: Throwing an Integer

cpp
#include <iostream>

double divide(double a, double b) {
if (b == 0) {
throw 0; // Throwing an integer to indicate division by zero
}
return a / b;
}

int main() {
try {
std::cout << "10 / 2 = " << divide(10, 2) << std::endl;
std::cout << "10 / 0 = " << divide(10, 0) << std::endl; // This will throw an exception
} catch (int e) {
std::cout << "Error: Division by zero!" << std::endl;
}

return 0;
}

Output:

10 / 2 = 5
Error: Division by zero!

In this example, when we try to divide by zero, the function divide throws an integer value 0. The catch block in main catches this integer exception and displays an error message.

Throwing C++ Standard Exceptions

The C++ Standard Library provides a hierarchy of exception classes that you can use. These classes are defined in the <stdexcept> header and include:

  • std::exception: Base class for all standard exceptions
  • std::runtime_error: Runtime errors
  • std::logic_error: Logic errors
  • And many more specialized exceptions

Example: Using Standard Exceptions

cpp
#include <iostream>
#include <stdexcept>
#include <string>

double divide(double a, double b) {
if (b == 0) {
throw std::runtime_error("Division by zero is not allowed");
}
return a / b;
}

int main() {
try {
std::cout << "10 / 2 = " << divide(10, 2) << std::endl;
std::cout << "10 / 0 = " << divide(10, 0) << std::endl; // This will throw an exception
} catch (const std::exception& e) {
std::cout << "Error: " << e.what() << std::endl;
}

return 0;
}

Output:

10 / 2 = 5
Error: Division by zero is not allowed

In this improved example, we throw a std::runtime_error with a descriptive message. When caught, we can use the what() method to get the error message.

Throwing Custom Exceptions

For more specific error situations, you might want to create your own exception classes that inherit from std::exception.

Example: Custom Exception Class

cpp
#include <iostream>
#include <stdexcept>
#include <string>

// Custom exception class
class DivisionByZeroError : public std::runtime_error {
public:
DivisionByZeroError(const std::string& message)
: std::runtime_error(message) {}
};

double divide(double a, double b) {
if (b == 0) {
throw DivisionByZeroError("Cannot divide " + std::to_string(a) + " by zero");
}
return a / b;
}

int main() {
try {
std::cout << "10 / 2 = " << divide(10, 2) << std::endl;
std::cout << "10 / 0 = " << divide(10, 0) << std::endl; // This will throw our custom exception
} catch (const DivisionByZeroError& e) {
std::cout << "Division Error: " << e.what() << std::endl;
} catch (const std::exception& e) {
std::cout << "Generic Error: " << e.what() << std::endl;
}

return 0;
}

Output:

10 / 2 = 5
Division Error: Cannot divide 10.000000 by zero

This custom exception allows us to:

  1. Handle specific types of errors differently
  2. Include more context-specific information
  3. Maintain a clear hierarchy of error types

Re-throwing Exceptions

Sometimes you might catch an exception but then decide you can't fully handle it. In that case, you can re-throw the exception for outer catch blocks to handle.

Example: Re-throwing an Exception

cpp
#include <iostream>
#include <stdexcept>

void handleException() {
try {
throw std::runtime_error("Original error");
} catch (const std::exception& e) {
std::cout << "First handler: " << e.what() << std::endl;

// Re-throw the same exception
throw;
}
}

int main() {
try {
handleException();
} catch (const std::exception& e) {
std::cout << "Second handler: " << e.what() << std::endl;
}

return 0;
}

Output:

First handler: Original error
Second handler: Original error

In this example, we catch the exception in handleException(), do some processing, and then re-throw it to be caught again in main().

Throwing in a Class Hierarchy

When working with class hierarchies, you often want to throw specialized exceptions while catching more general ones.

Example: Exception Hierarchy

cpp
#include <iostream>
#include <stdexcept>
#include <vector>

void accessVector(const std::vector<int>& vec, size_t index) {
if (index >= vec.size()) {
throw std::out_of_range("Index " + std::to_string(index) +
" is out of range (size: " +
std::to_string(vec.size()) + ")");
}
std::cout << "Value at index " << index << ": " << vec[index] << std::endl;
}

int main() {
std::vector<int> numbers = {10, 20, 30, 40, 50};

try {
accessVector(numbers, 2); // This is fine
accessVector(numbers, 10); // This will throw an exception
} catch (const std::out_of_range& e) {
std::cout << "Out of range error: " << e.what() << std::endl;
} catch (const std::logic_error& e) {
std::cout << "Logic error: " << e.what() << std::endl;
} catch (const std::exception& e) {
std::cout << "Standard exception: " << e.what() << std::endl;
}

return 0;
}

Output:

Value at index 2: 30
Out of range error: Index 10 is out of range (size: 5)

This example shows how to throw a specific type of exception (std::out_of_range) that is caught by a matching catch handler, even though there are other more general handlers available.

Real-World Application: Resource Management

Here's a practical example showing how to use exceptions for resource management in a file handling scenario:

cpp
#include <iostream>
#include <fstream>
#include <stdexcept>
#include <string>

class FileReader {
private:
std::ifstream file;
std::string filename;

public:
FileReader(const std::string& filename) : filename(filename) {
file.open(filename);
if (!file.is_open()) {
throw std::runtime_error("Failed to open file: " + filename);
}
}

~FileReader() {
if (file.is_open()) {
file.close();
}
}

std::string readLine() {
std::string line;
if (!std::getline(file, line) && !file.eof()) {
throw std::runtime_error("Error reading from file: " + filename);
}
return line;
}

bool hasMoreLines() {
return !file.eof();
}
};

int main() {
try {
// Try to open a file that may not exist
FileReader reader("config.txt");

std::cout << "File contents:" << std::endl;
while (reader.hasMoreLines()) {
std::cout << reader.readLine() << std::endl;
}
} catch (const std::exception& e) {
std::cerr << "Error: " << e.what() << std::endl;
std::cerr << "Please check if the file exists and you have read permissions." << std::endl;
return 1;
}

return 0;
}

If the file doesn't exist or can't be opened, a runtime_error exception is thrown and caught in main(), allowing for graceful error handling.

Best Practices for Using throw

  1. Be specific with exception types: Throw the most specific exception type that accurately represents the error.

  2. Include detailed error messages: Your exception messages should provide enough information to understand what went wrong and potentially how to fix it.

  3. Don't overuse exceptions: Use exceptions for exceptional conditions, not for normal control flow.

  4. Throw by value, catch by reference: This is the recommended pattern to avoid object slicing.

  5. Document the exceptions your functions might throw: Make it clear in your function documentation what exceptions might be thrown.

  6. Consider exception safety: Make sure your code cleans up resources properly even when exceptions are thrown.

  7. Avoid throwing exceptions in destructors: This can lead to program termination if an exception is already being handled.

Summary

The throw keyword in C++ is a powerful tool for error handling that allows you to:

  • Signal exceptional conditions in your code
  • Transfer control to exception handlers
  • Provide detailed information about what went wrong
  • Create a hierarchy of error types for precise handling

By understanding how to use throw properly, you can write more robust C++ programs that gracefully handle errors and unexpected situations.

Exercises

  1. Create a function that validates a username and throws different types of exceptions based on various validation rules (e.g., too short, contains invalid characters, etc.).

  2. Implement a simple stack class that throws exceptions for operations like pop on an empty stack.

  3. Write a program that reads data from a CSV file and throws appropriate exceptions for different types of formatting errors.

  4. Extend the FileReader example to handle different types of file errors with specific exception types.

  5. Implement a function that performs a binary search on a sorted array and throws appropriate exceptions for invalid inputs.

Additional Resources



If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)