Manejar excepciones en C++

C++C++Beginner
Practicar Ahora

💡 Este tutorial está traducido por IA desde la versión en inglés. Para ver la versión original, puedes hacer clic aquí

Introducción

En este laboratorio, aprenderás los conceptos fundamentales del manejo de excepciones en C++. Comenzarás por entender cómo lanzar y capturar excepciones básicas, que son una forma de manejar errores en tiempo de ejecución o situaciones inesperadas en tu programa. Luego, explorarás el uso de las palabras clave try, catch y throw, así como de las clases de excepciones estándar como std::exception. Además, aprenderás cómo definir clases de excepciones personalizadas e implementar múltiples bloques catch para diferentes tipos de excepciones. Finalmente, explorarás el uso de bloques try-catch anidados para manejar excepciones en diferentes niveles de tu programa.


Skills Graph

%%%%{init: {'theme':'neutral'}}%%%% flowchart RL cpp(("C++")) -.-> cpp/OOPGroup(["OOP"]) cpp(("C++")) -.-> cpp/AdvancedConceptsGroup(["Advanced Concepts"]) cpp(("C++")) -.-> cpp/StandardLibraryGroup(["Standard Library"]) cpp/OOPGroup -.-> cpp/classes_objects("Classes/Objects") cpp/OOPGroup -.-> cpp/class_methods("Class Methods") cpp/OOPGroup -.-> cpp/constructors("Constructors") cpp/OOPGroup -.-> cpp/inheritance("Inheritance") cpp/AdvancedConceptsGroup -.-> cpp/exceptions("Exceptions") cpp/StandardLibraryGroup -.-> cpp/standard_containers("Standard Containers") subgraph Lab Skills cpp/classes_objects -.-> lab-446082{{"Manejar excepciones en C++"}} cpp/class_methods -.-> lab-446082{{"Manejar excepciones en C++"}} cpp/constructors -.-> lab-446082{{"Manejar excepciones en C++"}} cpp/inheritance -.-> lab-446082{{"Manejar excepciones en C++"}} cpp/exceptions -.-> lab-446082{{"Manejar excepciones en C++"}} cpp/standard_containers -.-> lab-446082{{"Manejar excepciones en C++"}} end

Lanzar y capturar excepciones básicas

En este paso, aprenderás los conceptos fundamentales del manejo de excepciones en C++, centrándote en cómo lanzar y capturar excepciones básicas. Las excepciones son una forma de manejar errores en tiempo de ejecución o situaciones inesperadas en tu programa.

Abre el WebIDE y crea un nuevo archivo llamado basic_exceptions.cpp en el directorio ~/project:

touch ~/project/basic_exceptions.cpp

Agrega el siguiente código a basic_exceptions.cpp:

#include <iostream>
#include <stdexcept>

int divide(int numerator, int denominator) {
    // Throw an exception if denominator is zero
    if (denominator == 0) {
        throw std::runtime_error("Division by zero is not allowed!");
    }
    return numerator / denominator;
}

int main() {
    try {
        // Attempt a normal division
        int result1 = divide(10, 2);
        std::cout << "10 / 2 = " << result1 << std::endl;

        // Attempt division by zero
        int result2 = divide(10, 0);
        std::cout << "This line will not be executed" << std::endl;
    }
    catch (const std::exception& e) {
        // Catch and handle the exception
        std::cout << "Error: " << e.what() << std::endl;
    }

    return 0;
}

Analicemos los componentes clave:

  1. Palabra clave throw:

    • Se utiliza para generar una excepción cuando ocurre un error
    • En este ejemplo, lanzamos una std::runtime_error cuando se intenta una división por cero
  2. Bloque try:

    • Contiene código que puede generar una excepción
    • Te permite intentar operaciones riesgosas
    • Si ocurre una excepción, el control del programa se mueve al bloque catch
  3. Bloque catch:

    • Maneja la excepción lanzada en el bloque try
    • Captura excepciones de un tipo específico (en este caso, std::exception)
    • Utiliza e.what() para obtener el mensaje de error

Compila y ejecuta el programa:

g++ basic_exceptions.cpp -o basic_exceptions
./basic_exceptions

Ejemplo de salida:

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

Puntos clave sobre el manejo básico de excepciones:

  • Las excepciones proporcionan una forma de manejar errores en tiempo de ejecución de manera elegante
  • throw genera una excepción
  • try y catch trabajan juntos para manejar situaciones excepcionales
  • Evita que el programa se bloquee al proporcionar un manejo controlado de errores

Comprender las palabras clave try, catch y throw

En este paso, profundizarás en las tres palabras clave fundamentales del manejo de excepciones en C++: try, catch y throw. Estas palabras clave trabajan juntas para crear un sólido mecanismo de manejo de errores en tus programas.

Abre el WebIDE y crea un nuevo archivo llamado exception_keywords.cpp en el directorio ~/project:

touch ~/project/exception_keywords.cpp

Agrega el siguiente código a exception_keywords.cpp:

#include <iostream>
#include <string>

// Function that might throw an exception
int processAge(int age) {
    // Throw an exception for invalid age
    if (age < 0) {
        throw std::string("Age cannot be negative");
    }
    if (age > 120) {
        throw std::string("Age is unrealistically high");
    }
    return age;
}

int main() {
    // First try block: handling age validation
    try {
        // Successful case
        int validAge = processAge(25);
        std::cout << "Valid age: " << validAge << std::endl;

        // This will throw an exception
        int invalidAge1 = processAge(-5);
        std::cout << "This line will not be executed" << std::endl;
    }
    catch (const std::string& errorMessage) {
        // Catch block to handle the thrown exception
        std::cout << "Error: " << errorMessage << std::endl;
    }

    // Second try block: another example
    try {
        // This will throw another exception
        int invalidAge2 = processAge(150);
        std::cout << "This line will also not be executed" << std::endl;
    }
    catch (const std::string& errorMessage) {
        std::cout << "Error: " << errorMessage << std::endl;
    }

    return 0;
}

Analicemos los componentes clave:

  1. Palabra clave throw:

    • Se utiliza para generar una excepción cuando se cumple una condición específica
    • Puede lanzar diferentes tipos de objetos (cadenas, enteros, objetos personalizados)
    • Detiene inmediatamente la ejecución de la función actual
  2. Bloque try:

    • Contiene código que puede generar una excepción
    • Te permite intentar operaciones riesgosas
    • Si ocurre una excepción, el control del programa se mueve al bloque catch coincidente
  3. Bloque catch:

    • Captura y maneja tipos específicos de excepciones
    • Puede haber múltiples bloques catch para diferentes tipos de excepciones
    • Evita que el programa se bloquee manejando los errores de manera elegante

Compila y ejecuta el programa:

g++ exception_keywords.cpp -o exception_keywords
./exception_keywords

Ejemplo de salida:

Valid age: 25
Error: Age cannot be negative
Error: Age is unrealistically high

Puntos clave sobre las palabras clave de excepciones:

  • throw señala una condición de error
  • try define un bloque de código que puede generar una excepción
  • catch maneja la excepción y evita la terminación del programa
  • Las excepciones proporcionan una forma estructurada de manejar errores en tiempo de ejecución

Utilizar clases de excepciones estándar (std::exception)

En este paso, aprenderás sobre las clases de excepciones estándar en C++ y cómo utilizar la jerarquía de std::exception para manejar diferentes tipos de errores en tiempo de ejecución. La Biblioteca Estándar de C++ proporciona un conjunto de clases de excepciones predefinidas que cubren diversos escenarios de error.

Abre el WebIDE y crea un nuevo archivo llamado standard_exceptions.cpp en el directorio ~/project:

touch ~/project/standard_exceptions.cpp

Agrega el siguiente código a standard_exceptions.cpp:

#include <iostream>
#include <stdexcept>
#include <limits>

double divideNumbers(double numerator, double denominator) {
    // Check for division by zero using std::runtime_error
    if (denominator == 0) {
        throw std::runtime_error("Division by zero is not allowed!");
    }
    return numerator / denominator;
}

void checkArrayIndex(int* arr, int size, int index) {
    // Check for out-of-range access using std::out_of_range
    if (index < 0 || index >= size) {
        throw std::out_of_range("Array index is out of bounds!");
    }
    std::cout << "Value at index " << index << ": " << arr[index] << std::endl;
}

int main() {
    try {
        // Demonstrate division by zero exception
        std::cout << "Attempting division:" << std::endl;
        double result = divideNumbers(10, 0);
    }
    catch (const std::runtime_error& e) {
        std::cout << "Runtime Error: " << e.what() << std::endl;
    }

    try {
        // Demonstrate array index out of range exception
        int numbers[] = {1, 2, 3, 4, 5};
        int arraySize = 5;

        std::cout << "\nAccessing array elements:" << std::endl;
        checkArrayIndex(numbers, arraySize, 2);  // Valid index
        checkArrayIndex(numbers, arraySize, 10); // Invalid index
    }
    catch (const std::out_of_range& e) {
        std::cout << "Out of Range Error: " << e.what() << std::endl;
    }

    return 0;
}

Exploremos las clases de excepciones estándar:

  1. std::exception:

    • Clase base para todas las excepciones estándar
    • Proporciona un método virtual what() para obtener la descripción del error
  2. Clases de excepciones derivadas comunes:

    • std::runtime_error: Para errores en tiempo de ejecución que solo se pueden detectar durante la ejecución del programa
    • std::out_of_range: Cuando un índice o iterador está fuera del rango válido
    • Otras clases comunes incluyen std::logic_error, std::invalid_argument, etc.

Compila y ejecuta el programa:

g++ standard_exceptions.cpp -o standard_exceptions
./standard_exceptions

Ejemplo de salida:

Attempting division:
Runtime Error: Division by zero is not allowed!

Accessing array elements:
Value at index 2: 3
Out of Range Error: Array index is out of bounds!

Puntos clave sobre las clases de excepciones estándar:

  • Proporcionan una forma estructurada de manejar diferentes tipos de errores
  • Cada clase de excepción tiene un propósito específico
  • El método what() devuelve un mensaje de error descriptivo
  • Ayudan a crear un manejo de errores más robusto e informativo

Definir clases de excepciones personalizadas

En este paso, aprenderás cómo crear tus propias clases de excepciones personalizadas en C++. Las excepciones personalizadas te permiten definir tipos de error específicos adaptados a las necesidades únicas de tu aplicación.

Abre el WebIDE y crea un nuevo archivo llamado custom_exceptions.cpp en el directorio ~/project:

touch ~/project/custom_exceptions.cpp

Agrega el siguiente código a custom_exceptions.cpp:

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

// Custom exception class for bank account errors
class InsufficientFundsException : public std::runtime_error {
public:
    // Constructor that takes account balance and withdrawal amount
    InsufficientFundsException(double balance, double amount)
        : std::runtime_error("Insufficient funds"),
          currentBalance(balance),
          withdrawalAmount(amount) {}

    // Method to get detailed error information
    double getCurrentBalance() const { return currentBalance; }
    double getWithdrawalAmount() const { return withdrawalAmount; }

private:
    double currentBalance;
    double withdrawalAmount;
};

class BankAccount {
private:
    double balance;

public:
    BankAccount(double initialBalance) : balance(initialBalance) {}

    void withdraw(double amount) {
        // Check if withdrawal amount exceeds current balance
        if (amount > balance) {
            throw InsufficientFundsException(balance, amount);
        }
        balance -= amount;
        std::cout << "Withdrawal successful. Remaining balance: $"
                  << balance << std::endl;
    }

    double getBalance() const { return balance; }
};

int main() {
    try {
        // Create a bank account with initial balance
        BankAccount account(100.0);

        // Attempt a valid withdrawal
        account.withdraw(50.0);

        // Attempt an invalid withdrawal
        account.withdraw(75.0);
    }
    catch (const InsufficientFundsException& e) {
        std::cout << "Error: " << e.what() << std::endl;
        std::cout << "Current Balance: $" << e.getCurrentBalance() << std::endl;
        std::cout << "Attempted Withdrawal: $" << e.getWithdrawalAmount() << std::endl;
    }

    return 0;
}

Analicemos la clase de excepción personalizada:

  1. Herencia de std::runtime_error:

    • Proporciona una base para las clases de excepciones personalizadas
    • Permite utilizar el método what() para obtener la descripción del error
  2. Características de la excepción personalizada:

    • Constructor con contexto de error adicional
    • Métodos para recuperar detalles específicos del error
    • Proporciona un manejo de errores más informativo

Compila y ejecuta el programa:

g++ custom_exceptions.cpp -o custom_exceptions
./custom_exceptions

Ejemplo de salida:

Withdrawal successful. Remaining balance: $50
Error: Insufficient funds
Current Balance: $50
Attempted Withdrawal: $75

Puntos clave sobre las clases de excepciones personalizadas:

  • Heredan de las clases de excepciones estándar
  • Añaden contexto de error específico y métodos
  • Proporcionan información de error más detallada
  • Mejoran el manejo de errores y la depuración

Implementar múltiples bloques catch para diferentes tipos de excepciones

En este paso, aprenderás cómo manejar múltiples tipos de excepciones utilizando diferentes bloques catch. Este enfoque te permite proporcionar un manejo de errores específico para varios tipos de excepciones que pueden ocurrir en tu programa.

Abre el WebIDE y crea un nuevo archivo llamado multiple_catch.cpp en el directorio ~/project:

touch ~/project/multiple_catch.cpp

Agrega el siguiente código a multiple_catch.cpp:

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

class InvalidAgeException : public std::runtime_error {
public:
    InvalidAgeException(int age)
        : std::runtime_error("Invalid age"), invalidAge(age) {}

    int getInvalidAge() const { return invalidAge; }

private:
    int invalidAge;
};

class Student {
private:
    std::string name;
    int age;

public:
    void setStudent(const std::string& studentName, int studentAge) {
        // Validate name length
        if (studentName.length() < 2) {
            throw std::length_error("Name is too short");
        }

        // Validate age range
        if (studentAge < 0 || studentAge > 120) {
            throw InvalidAgeException(studentAge);
        }

        name = studentName;
        age = studentAge;
    }

    void displayInfo() const {
        std::cout << "Name: " << name << ", Age: " << age << std::endl;
    }
};

int main() {
    Student student;

    // First attempt: Short name
    try {
        student.setStudent("A", 25);
    }
    catch (const std::length_error& e) {
        std::cout << "Length Error: " << e.what() << std::endl;
    }
    catch (const InvalidAgeException& e) {
        std::cout << "Age Error: " << e.what()
                  << " (" << e.getInvalidAge() << ")" << std::endl;
    }

    // Second attempt: Invalid age
    try {
        student.setStudent("John Doe", 150);
    }
    catch (const std::length_error& e) {
        std::cout << "Length Error: " << e.what() << std::endl;
    }
    catch (const InvalidAgeException& e) {
        std::cout << "Age Error: " << e.what()
                  << " (" << e.getInvalidAge() << ")" << std::endl;
    }

    // Successful attempt
    try {
        student.setStudent("Alice", 20);
        student.displayInfo();
    }
    catch (const std::length_error& e) {
        std::cout << "Length Error: " << e.what() << std::endl;
    }
    catch (const InvalidAgeException& e) {
        std::cout << "Age Error: " << e.what()
                  << " (" << e.getInvalidAge() << ")" << std::endl;
    }

    return 0;
}

Analicemos los múltiples bloques catch:

  1. Múltiples bloques catch:

    • Permiten manejar diferentes tipos de excepciones
    • Se ejecutan en orden desde el más específico al más general
    • Cada bloque puede manejar un tipo de excepción específico
  2. Estrategia de manejo de excepciones:

    • El primer bloque catch maneja std::length_error
    • El segundo bloque catch maneja InvalidAgeException
    • Proporciona mensajes de error específicos para diferentes escenarios

Compila y ejecuta el programa:

g++ multiple_catch.cpp -o multiple_catch
./multiple_catch

Ejemplo de salida:

Length Error: Name is too short
Age Error: Invalid age (150)
Name: Alice, Age: 20

Puntos clave sobre los múltiples bloques catch:

  • Manejan diferentes tipos de excepciones por separado
  • Proporcionan un manejo de errores específico para cada excepción
  • El orden es importante al capturar excepciones
  • Permiten un manejo de errores más detallado

Utilizar bloques try-catch anidados

En este paso, aprenderás cómo utilizar bloques try-catch anidados para manejar escenarios de error complejos y proporcionar un manejo de excepciones más detallado. Los bloques try-catch anidados te permiten manejar excepciones en diferentes niveles de tu código.

Abre el WebIDE y crea un nuevo archivo llamado nested_exceptions.cpp en el directorio ~/project:

touch ~/project/nested_exceptions.cpp

Agrega el siguiente código a nested_exceptions.cpp:

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

class FileReadError : public std::runtime_error {
public:
    FileReadError(const std::string& filename)
        : std::runtime_error("Error reading file"), fileName(filename) {}

    std::string getFileName() const { return fileName; }

private:
    std::string fileName;
};

class DataProcessor {
public:
    void processFile(const std::string& filename) {
        try {
            // Simulate file reading
            if (filename.empty()) {
                throw FileReadError("Empty filename");
            }

            std::cout << "Reading file: " << filename << std::endl;

            try {
                // Simulate data processing
                validateData(filename);
            }
            catch (const std::runtime_error& e) {
                std::cout << "Inner try-catch: Data validation error" << std::endl;
                std::cout << "Error details: " << e.what() << std::endl;

                // Rethrow the exception to outer catch block
                throw;
            }
        }
        catch (const FileReadError& e) {
            std::cout << "Outer try-catch: File read error" << std::endl;
            std::cout << "Filename: " << e.getFileName() << std::endl;
        }
        catch (...) {
            std::cout << "Caught unknown exception" << std::endl;
        }
    }

private:
    void validateData(const std::string& filename) {
        // Simulate data validation
        if (filename == "corrupt.txt") {
            throw std::runtime_error("Corrupt file data");
        }
        std::cout << "Data validation successful" << std::endl;
    }
};

int main() {
    DataProcessor processor;

    // Scenario 1: Empty filename
    std::cout << "Scenario 1: Empty Filename" << std::endl;
    processor.processFile("");

    // Scenario 2: Corrupt file
    std::cout << "\nScenario 2: Corrupt File" << std::endl;
    processor.processFile("corrupt.txt");

    // Scenario 3: Valid file
    std::cout << "\nScenario 3: Valid File" << std::endl;
    processor.processFile("data.txt");

    return 0;
}

Analicemos los bloques try-catch anidados:

  1. Bloque try-catch exterior:

    • Maneja excepciones a nivel de archivo
    • Captura FileReadError y otros posibles errores
  2. Bloque try-catch interior:

    • Maneja excepciones específicas del procesamiento de datos
    • Puede volver a lanzar excepciones al bloque catch exterior
  3. Manejador general (catch (...)):

    • Captura cualquier excepción inesperada
    • Proporciona una capa final de manejo de errores

Compila y ejecuta el programa:

g++ nested_exceptions.cpp -o nested_exceptions
./nested_exceptions

Ejemplo de salida:

Scenario 1: Empty Filename
Outer try-catch: File read error
Filename: Empty filename

Scenario 2: Corrupt File
Reading file: corrupt.txt
Inner try-catch: Data validation error
Error details: Corrupt file data

Scenario 3: Valid File
Reading file: data.txt
Data validation successful

Puntos clave sobre los bloques try-catch anidados:

  • Proporcionan múltiples niveles de manejo de excepciones
  • Permiten un manejo de errores más detallado
  • Pueden volver a lanzar excepciones a bloques catch exteriores
  • Son útiles para escenarios de error complejos

Resumen

En este laboratorio, aprendiste los conceptos fundamentales del manejo de excepciones en C++. Comenzaste por entender cómo lanzar y capturar excepciones básicas utilizando las palabras clave throw, try y catch. Exploraste el uso de la clase de excepción std::runtime_error para manejar errores de división por cero. Además, aprendiste cómo acceder al mensaje de error utilizando la función e.what() dentro del bloque catch. Estas técnicas proporcionan una forma de manejar los errores en tiempo de ejecución de manera elegante y evitar que el programa se bloquee, lo que te permite escribir aplicaciones de C++ más robustas y confiables.