Lidar com Exceções em C++

C++Beginner
Pratique Agora

Introdução

Neste laboratório, você aprenderá os conceitos fundamentais de tratamento de exceções em C++. Começará por entender como lançar (throw) e capturar (catch) exceções básicas, que são uma forma de lidar com erros em tempo de execução ou situações inesperadas em seu programa. Em seguida, explorará o uso das palavras-chave try, catch e throw, bem como classes de exceção padrão como std::exception. Adicionalmente, aprenderá como definir classes de exceção personalizadas e implementar múltiplos blocos catch para diferentes tipos de exceção. Finalmente, explorará o uso de blocos try-catch aninhados para lidar com exceções em diferentes níveis do seu programa.

Este é um Lab Guiado, que fornece instruções passo a passo para ajudá-lo a aprender e praticar. Siga as instruções cuidadosamente para completar cada etapa e ganhar experiência prática. Dados históricos mostram que este é um laboratório de nível iniciante com uma taxa de conclusão de 82%. Recebeu uma taxa de avaliações positivas de 100% dos estudantes.

Lançar e Capturar Exceções Básicas

Nesta etapa, você aprenderá os conceitos fundamentais de tratamento de exceções em C++, focando em como lançar (throw) e capturar (catch) exceções básicas. Exceções são uma forma de lidar com erros em tempo de execução ou situações inesperadas em seu programa.

Abra o WebIDE e crie um novo arquivo chamado basic_exceptions.cpp no diretório ~/project:

touch ~/project/basic_exceptions.cpp

Adicione o seguinte 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;
}

Vamos detalhar os componentes-chave:

  1. Palavra-chave throw:

    • Usada para gerar uma exceção quando um erro ocorre
    • Neste exemplo, lançamos um std::runtime_error quando a divisão por zero é tentada
  2. Bloco try:

    • Contém código que pode gerar uma exceção
    • Permite que você tente operações arriscadas
    • Se uma exceção ocorrer, o controle do programa se move para o bloco catch
  3. Bloco catch:

    • Lida com a exceção lançada no bloco try
    • Captura exceções de um tipo específico (aqui, std::exception)
    • Usa e.what() para obter a mensagem de erro

Compile e execute o programa:

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

Exemplo de saída:

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

Pontos-chave sobre o tratamento básico de exceções:

  • Exceções fornecem uma maneira de lidar com erros em tempo de execução de forma elegante
  • throw gera uma exceção
  • try e catch trabalham juntos para gerenciar situações excepcionais
  • Evita falhas no programa, fornecendo tratamento de erros controlado

Entenda as Palavras-Chave try, catch e throw

Nesta etapa, você se aprofundará nas três palavras-chave principais do tratamento de exceções em C++: try, catch e throw. Essas palavras-chave trabalham juntas para criar um mecanismo robusto de tratamento de erros em seus programas.

Abra o WebIDE e crie um novo arquivo chamado exception_keywords.cpp no diretório ~/project:

touch ~/project/exception_keywords.cpp

Adicione o seguinte 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;
}

Vamos detalhar os componentes-chave:

  1. Palavra-chave throw:

    • Usada para gerar uma exceção quando uma condição específica é atendida
    • Pode lançar diferentes tipos de objetos (strings, inteiros, objetos personalizados)
    • Interrompe imediatamente a execução da função atual
  2. Bloco try:

    • Contém código que pode gerar uma exceção
    • Permite que você tente operações arriscadas
    • Se uma exceção ocorrer, o controle do programa se move para o bloco catch correspondente
  3. Bloco catch:

    • Captura e lida com tipos específicos de exceções
    • Pode ter múltiplos blocos catch para diferentes tipos de exceção
    • Impede que o programa trave, tratando os erros de forma elegante

Compile e execute o programa:

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

Exemplo de saída:

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

Pontos-chave sobre as palavras-chave de exceção:

  • throw sinaliza uma condição de erro
  • try define um bloco de código que pode gerar uma exceção
  • catch lida com a exceção e impede a terminação do programa
  • Exceções fornecem uma maneira estruturada de gerenciar erros em tempo de execução

Usar Classes de Exceção Padrão (std::exception)

Nesta etapa, você aprenderá sobre as classes de exceção padrão em C++ e como usar a hierarquia std::exception para lidar com diferentes tipos de erros em tempo de execução. A Biblioteca Padrão C++ fornece um conjunto de classes de exceção predefinidas que cobrem vários cenários de erro.

Abra o WebIDE e crie um novo arquivo chamado standard_exceptions.cpp no diretório ~/project:

touch ~/project/standard_exceptions.cpp

Adicione o seguinte 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;
}

Vamos explorar as classes de exceção padrão:

  1. std::exception:

    • Classe base para todas as exceções padrão
    • Fornece um método virtual what() para obter a descrição do erro
  2. Classes de exceção derivadas comuns:

    • std::runtime_error: Para erros em tempo de execução que só podem ser detectados durante a execução do programa
    • std::out_of_range: Quando um índice ou iterador está fora do intervalo válido
    • Outras classes comuns incluem std::logic_error, std::invalid_argument, etc.

Compile e execute o programa:

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

Exemplo de saída:

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!

Pontos-chave sobre as classes de exceção padrão:

  • Fornecem uma maneira estruturada de lidar com diferentes tipos de erros
  • Cada classe de exceção serve a um propósito específico
  • O método what() retorna uma mensagem de erro descritiva
  • Ajuda a criar um tratamento de erros mais robusto e informativo

Definir Classes de Exceção Personalizadas

Nesta etapa, você aprenderá como criar suas próprias classes de exceção personalizadas em C++. Exceções personalizadas permitem que você defina tipos de erro específicos adaptados aos requisitos exclusivos de sua aplicação.

Abra o WebIDE e crie um novo arquivo chamado custom_exceptions.cpp no diretório ~/project:

touch ~/project/custom_exceptions.cpp

Adicione o seguinte 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;
}

Vamos detalhar a classe de exceção personalizada:

  1. Herança de std::runtime_error:

    • Fornece uma base para classes de exceção personalizadas
    • Permite usar o método what() para obter a descrição do erro
  2. Recursos de Exceção Personalizada:

    • Construtor com contexto de erro adicional
    • Métodos para recuperar detalhes específicos do erro
    • Fornece um tratamento de erros mais informativo

Compile e execute o programa:

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

Exemplo de saída:

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

Pontos-chave sobre as classes de exceção personalizadas:

  • Herda de classes de exceção padrão
  • Adiciona contexto e métodos de erro específicos
  • Fornece informações de erro mais detalhadas
  • Melhora o tratamento de erros e a depuração

Implementar Múltiplos Blocos catch para Diferentes Tipos de Exceção

Nesta etapa, você aprenderá como lidar com múltiplos tipos de exceção usando diferentes blocos catch. Essa abordagem permite que você forneça tratamento de erro específico para vários tipos de exceções que podem ocorrer em seu programa.

Abra o WebIDE e crie um novo arquivo chamado multiple_catch.cpp no diretório ~/project:

touch ~/project/multiple_catch.cpp

Adicione o seguinte 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;
}

Vamos detalhar os múltiplos blocos catch:

  1. Múltiplos blocos catch:

    • Permitem o tratamento de diferentes tipos de exceção
    • São executados em ordem, do mais específico para o mais geral
    • Cada bloco pode lidar com um tipo específico de exceção
  2. Estratégia de Tratamento de Exceção:

    • O primeiro bloco catch lida com std::length_error
    • O segundo bloco catch lida com InvalidAgeException
    • Fornece mensagens de erro específicas para diferentes cenários

Compile e execute o programa:

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

Exemplo de saída:

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

Pontos-chave sobre múltiplos blocos catch:

  • Lidam com diferentes tipos de exceção separadamente
  • Fornecem tratamento de erro específico para cada exceção
  • A ordem importa ao capturar exceções
  • Permite um gerenciamento de erros mais granular

Usar Blocos try-catch Aninhados

Nesta etapa, você aprenderá como usar blocos try-catch aninhados para lidar com cenários de erro complexos e fornecer um tratamento de exceção mais granular. Blocos try-catch aninhados permitem que você lide com exceções em diferentes níveis do seu código.

Abra o WebIDE e crie um novo arquivo chamado nested_exceptions.cpp no diretório ~/project:

touch ~/project/nested_exceptions.cpp

Adicione o seguinte 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;
}

Vamos detalhar os blocos try-catch aninhados:

  1. Bloco try-catch externo:

    • Lida com exceções em nível de arquivo
    • Captura FileReadError e outros erros potenciais
  2. Bloco try-catch interno:

    • Lida com exceções específicas do processamento de dados
    • Pode relançar (rethrow) exceções para o bloco catch externo
  3. Manipulador catch-all (catch (...)):

    • Captura quaisquer exceções inesperadas
    • Fornece uma camada final de tratamento de erros

Compile e execute o programa:

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

Exemplo de saída:

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

Pontos-chave sobre blocos try-catch aninhados:

  • Fornecem múltiplos níveis de tratamento de exceção
  • Permitem um gerenciamento de erros mais detalhado
  • Podem relançar exceções para blocos catch externos
  • Úteis para cenários de erro complexos

Resumo

Neste laboratório, você aprendeu os conceitos fundamentais de tratamento de exceções em C++. Você começou entendendo como lançar (throw) e capturar (catch) exceções básicas usando as palavras-chave throw, try e catch. Você explorou o uso da classe de exceção std::runtime_error para lidar com erros de divisão por zero. Além disso, você aprendeu como acessar a mensagem de erro usando a função e.what() dentro do bloco catch. Essas técnicas fornecem uma maneira de lidar com erros de tempo de execução de forma elegante e evitar travamentos do programa, permitindo que você escreva aplicações C++ mais robustas e confiáveis.