C++ 예외 처리

C++Beginner
지금 연습하기

소개

이 랩에서는 C++ 의 예외 처리의 기본적인 개념을 배우게 됩니다. 먼저 프로그램에서 런타임 오류나 예기치 않은 상황을 처리하는 방법인 기본적인 예외를 던지고 잡는 방법을 이해하는 것으로 시작합니다. 그런 다음 try, catch, 그리고 throw 키워드와 std::exception과 같은 표준 예외 클래스의 사용법을 살펴봅니다. 또한, 사용자 정의 예외 클래스를 정의하고, 서로 다른 예외 유형에 대해 여러 개의 catch 블록을 구현하는 방법을 배우게 됩니다. 마지막으로, 프로그램의 여러 수준에서 예외를 처리하기 위해 중첩된 try-catch 블록의 사용법을 살펴봅니다.

이것은 가이드 실험입니다. 학습과 실습을 돕기 위한 단계별 지침을 제공합니다.각 단계를 완료하고 실무 경험을 쌓기 위해 지침을 주의 깊게 따르세요. 과거 데이터에 따르면, 이것은 초급 레벨의 실험이며 완료율은 82%입니다.학습자들로부터 100%의 긍정적인 리뷰율을 받았습니다.

기본 예외 던지기 및 잡기

이 단계에서는 C++ 의 예외 처리의 기본적인 개념을 배우고, 기본 예외를 던지고 잡는 방법에 중점을 둡니다. 예외는 프로그램에서 런타임 오류나 예기치 않은 상황을 처리하는 방법입니다.

WebIDE 를 열고 ~/project 디렉토리에 basic_exceptions.cpp라는 새 파일을 만듭니다.

touch ~/project/basic_exceptions.cpp

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;
}

핵심 구성 요소를 살펴보겠습니다.

  1. throw 키워드:

    • 오류가 발생했을 때 예외를 생성하는 데 사용됩니다.
    • 이 예제에서는 0 으로 나누기를 시도할 때 std::runtime_error를 던집니다.
  2. try 블록:

    • 예외를 생성할 수 있는 코드를 포함합니다.
    • 위험한 작업을 시도할 수 있도록 합니다.
    • 예외가 발생하면 프로그램 제어가 catch 블록으로 이동합니다.
  3. catch 블록:

    • try 블록에서 던져진 예외를 처리합니다.
    • 특정 유형의 예외를 잡습니다 (여기서는 std::exception).
    • e.what()을 사용하여 오류 메시지를 가져옵니다.

프로그램을 컴파일하고 실행합니다.

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

예시 출력:

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

기본 예외 처리에 대한 주요 사항:

  • 예외는 런타임 오류를 적절하게 처리하는 방법을 제공합니다.
  • throw는 예외를 생성합니다.
  • trycatch는 예외적인 상황을 관리하기 위해 함께 작동합니다.
  • 제어된 오류 처리를 제공하여 프로그램 충돌을 방지합니다.

try, catch, throw 키워드 이해

이 단계에서는 C++ 의 예외 처리의 세 가지 핵심 키워드인 try, catch, 그리고 throw에 대해 자세히 알아보겠습니다. 이러한 키워드는 프로그램에서 강력한 오류 처리 메커니즘을 만들기 위해 함께 작동합니다.

WebIDE 를 열고 ~/project 디렉토리에 exception_keywords.cpp라는 새 파일을 만듭니다.

touch ~/project/exception_keywords.cpp

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;
}

핵심 구성 요소를 살펴보겠습니다.

  1. throw 키워드:

    • 특정 조건이 충족될 때 예외를 생성하는 데 사용됩니다.
    • 다양한 유형의 객체 (문자열, 정수, 사용자 정의 객체) 를 던질 수 있습니다.
    • 현재 함수의 실행을 즉시 중지합니다.
  2. try 블록:

    • 예외를 생성할 수 있는 코드를 포함합니다.
    • 위험한 작업을 시도할 수 있도록 합니다.
    • 예외가 발생하면 프로그램 제어가 일치하는 catch 블록으로 이동합니다.
  3. catch 블록:

    • 특정 유형의 예외를 잡고 처리합니다.
    • 서로 다른 예외 유형에 대해 여러 개의 catch 블록을 가질 수 있습니다.
    • 오류를 적절하게 처리하여 프로그램이 충돌하는 것을 방지합니다.

프로그램을 컴파일하고 실행합니다.

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

예시 출력:

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

예외 키워드에 대한 주요 사항:

  • throw는 오류 조건을 알립니다.
  • try는 예외를 생성할 수 있는 코드 블록을 정의합니다.
  • catch는 예외를 처리하고 프로그램 종료를 방지합니다.
  • 예외는 런타임 오류를 관리하는 구조화된 방법을 제공합니다.

표준 예외 클래스 사용 (std::exception)

이 단계에서는 C++ 의 표준 예외 클래스에 대해 배우고, std::exception 계층 구조를 사용하여 다양한 유형의 런타임 오류를 처리하는 방법을 배웁니다. C++ 표준 라이브러리는 다양한 오류 시나리오를 다루는 미리 정의된 예외 클래스 집합을 제공합니다.

WebIDE 를 열고 ~/project 디렉토리에 standard_exceptions.cpp라는 새 파일을 만듭니다.

touch ~/project/standard_exceptions.cpp

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;
}

표준 예외 클래스를 살펴보겠습니다.

  1. std::exception:

    • 모든 표준 예외의 기본 클래스입니다.
    • 오류 설명을 얻기 위한 가상 what() 메서드를 제공합니다.
  2. 일반적인 파생 예외 클래스:

    • std::runtime_error: 프로그램 실행 중에만 감지할 수 있는 런타임 오류에 사용됩니다.
    • std::out_of_range: 인덱스 또는 반복자가 유효 범위를 벗어난 경우에 사용됩니다.
    • 다른 일반적인 클래스에는 std::logic_error, std::invalid_argument 등이 있습니다.

프로그램을 컴파일하고 실행합니다.

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

예시 출력:

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!

표준 예외 클래스에 대한 주요 사항:

  • 다양한 유형의 오류를 처리하는 구조화된 방법을 제공합니다.
  • 각 예외 클래스는 특정 목적을 수행합니다.
  • what() 메서드는 설명적인 오류 메시지를 반환합니다.
  • 보다 강력하고 유익한 오류 처리를 만드는 데 도움이 됩니다.

사용자 정의 예외 클래스 정의

이 단계에서는 C++ 에서 사용자 정의 예외 클래스를 만드는 방법을 배웁니다. 사용자 정의 예외를 사용하면 애플리케이션의 고유한 요구 사항에 맞게 특정 오류 유형을 정의할 수 있습니다.

WebIDE 를 열고 ~/project 디렉토리에 custom_exceptions.cpp라는 새 파일을 만듭니다.

touch ~/project/custom_exceptions.cpp

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;
}

사용자 정의 예외 클래스를 자세히 살펴보겠습니다.

  1. std::runtime_error에서 상속:

    • 사용자 정의 예외 클래스의 기반을 제공합니다.
    • what() 메서드를 사용하여 오류 설명을 얻을 수 있습니다.
  2. 사용자 정의 예외 기능:

    • 추가 오류 컨텍스트가 있는 생성자
    • 특정 오류 세부 정보를 검색하는 메서드
    • 보다 유익한 오류 처리를 제공합니다.

프로그램을 컴파일하고 실행합니다.

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

예시 출력:

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

사용자 정의 예외 클래스에 대한 주요 사항:

  • 표준 예외 클래스에서 상속합니다.
  • 특정 오류 컨텍스트 및 메서드를 추가합니다.
  • 보다 자세한 오류 정보를 제공합니다.
  • 오류 처리 및 디버깅을 개선합니다.

다양한 예외 유형에 대한 여러 catch 블록 구현

이 단계에서는 서로 다른 catch 블록을 사용하여 여러 예외 유형을 처리하는 방법을 배웁니다. 이 접근 방식을 사용하면 프로그램에서 발생할 수 있는 다양한 유형의 예외에 대해 특정 오류 처리를 제공할 수 있습니다.

WebIDE 를 열고 ~/project 디렉토리에 multiple_catch.cpp라는 새 파일을 만듭니다.

touch ~/project/multiple_catch.cpp

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;
}

여러 catch 블록을 자세히 살펴보겠습니다.

  1. 여러 catch 블록:

    • 서로 다른 유형의 예외를 처리할 수 있습니다.
    • 가장 구체적인 것부터 가장 일반적인 순서로 실행됩니다.
    • 각 블록은 특정 예외 유형을 처리할 수 있습니다.
  2. 예외 처리 전략:

    • 첫 번째 catch 블록은 std::length_error를 처리합니다.
    • 두 번째 catch 블록은 InvalidAgeException을 처리합니다.
    • 서로 다른 시나리오에 대한 특정 오류 메시지를 제공합니다.

프로그램을 컴파일하고 실행합니다.

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

예시 출력:

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

여러 catch 블록에 대한 주요 사항:

  • 서로 다른 예외 유형을 개별적으로 처리합니다.
  • 각 예외에 대한 특정 오류 처리를 제공합니다.
  • 예외를 catch 할 때 순서가 중요합니다.
  • 보다 세분화된 오류 관리를 허용합니다.

중첩된 try-catch 블록 사용

이 단계에서는 복잡한 오류 시나리오를 처리하고 보다 세분화된 예외 처리를 제공하기 위해 중첩된 try-catch 블록을 사용하는 방법을 배웁니다. 중첩된 try-catch 블록을 사용하면 코드의 서로 다른 수준에서 예외를 처리할 수 있습니다.

WebIDE 를 열고 ~/project 디렉토리에 nested_exceptions.cpp라는 새 파일을 만듭니다.

touch ~/project/nested_exceptions.cpp

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;
}

중첩된 try-catch 블록을 자세히 살펴보겠습니다.

  1. 외부 try-catch 블록:

    • 파일 수준의 예외를 처리합니다.
    • FileReadError 및 기타 잠재적 오류를 catch 합니다.
  2. 내부 try-catch 블록:

    • 데이터 처리 관련 예외를 처리합니다.
    • 외부 catch 블록으로 예외를 다시 throw 할 수 있습니다.
  3. 모든 catch 핸들러 (catch (...)):

    • 예상치 못한 예외를 catch 합니다.
    • 최종 오류 처리 계층을 제공합니다.

프로그램을 컴파일하고 실행합니다.

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

예시 출력:

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

중첩된 try-catch 블록에 대한 주요 사항:

  • 여러 수준의 예외 처리를 제공합니다.
  • 보다 자세한 오류 관리를 허용합니다.
  • 외부 catch 블록으로 예외를 다시 throw 할 수 있습니다.
  • 복잡한 오류 시나리오에 유용합니다.

요약

이 Lab 에서는 C++ 에서 예외 처리의 기본 개념을 배웠습니다. throw, try, catch 키워드를 사용하여 기본적인 예외를 throw 하고 catch 하는 방법을 이해하는 것으로 시작했습니다. 0 으로 나누기 오류를 처리하기 위해 std::runtime_error 예외 클래스를 사용하는 방법을 살펴보았습니다. 또한 catch 블록 내에서 e.what() 함수를 사용하여 오류 메시지에 접근하는 방법을 배웠습니다. 이러한 기술은 런타임 오류를 적절하게 처리하고 프로그램 충돌을 방지하여 보다 강력하고 신뢰할 수 있는 C++ 애플리케이션을 작성할 수 있도록 합니다.