icon안동민 개발노트

예외 던지기 (throw)


throw 키워드의 기본 사용법

 C++에서 throw 키워드는 예외를 발생시키는 데 사용됩니다. 기본 구문은 다음과 같습니다.

throw exception_object;

 여기서 exception_object는 예외 객체입니다. 이는 내장 타입이나 사용자 정의 클래스의 객체일 수 있습니다.

예제
// 예제
#include <stdexcept>
 
void validateAge(int age) {
    if (age < 0) {
        throw std::invalid_argument("Age cannot be negative");
    }
    if (age > 150) {
        throw std::out_of_range("Age is unrealistically high");
    }
}
 
int main() {
    try {
        validateAge(-5);
    } catch (const std::exception& e) {
        std::cerr << "Caught exception: " << e.what() << std::endl;
    }
    return 0;
}

 이 예제에서 validateAge 함수는 나이가 유효하지 않을 때 예외를 던집니다.

다양한 타입의 예외 던지기

 C++에서는 다양한 타입의 객체를 예외로 던질 수 있습니다.

 내장 타입 던지기

throw 42;  // 정수 던지기
throw 3.14;  // 부동소수점 던지기
throw "Error occurred";  // 문자열 리터럴 던지기

 표준 예외 클래스 사용

#include <stdexcept>
 
void someFunction() {
    throw std::runtime_error("Runtime error occurred");
}
 
void anotherFunction() {
    throw std::out_of_range("Index out of range");
}

 사용자 정의 예외 클래스

class MyException : public std::exception {
public:
    const char* what() const noexcept override {
        return "My custom exception";
    }
};
 
void myFunction() {
    throw MyException();
}

함수에서 예외 던지기

 함수에서 예외를 던지면, 해당 함수의 실행이 즉시 중단되고 제어가 예외 처리 블록으로 이동합니다.

#include <iostream>
#include <stdexcept>
 
double divide(double a, double b) {
    if (b == 0) {
        throw std::domain_error("Division by zero");
    }
    return a / b;
}
 
int main() {
    try {
        std::cout << divide(10, 0) << std::endl;
    } catch (const std::domain_error& e) {
        std::cerr << "Error: " << e.what() << std::endl;
    }
    return 0;
}

생성자에서 예외 던지기

 생성자에서 예외를 던지면 객체의 생성이 중단되고, 이미 생성된 멤버 객체들의 소멸자가 호출됩니다.

#include <iostream>
#include <stdexcept>
 
class Resource {
public:
    Resource() {
        // 리소스 할당 실패 시 예외 던지기
        throw std::runtime_error("Resource allocation failed");
    }
};
 
class MyClass {
    Resource res;
public:
    MyClass() try : res() {
        // 생성자 본문
    } catch (const std::exception& e) {
        // 예외 처리
        std::cerr << "Error in constructor: " << e.what() << std::endl;
        throw; // 예외 재전파
    }
};
 
int main() {
    try {
        MyClass obj;
    } catch (const std::exception& e) {
        std::cerr << "Exception caught in main: " << e.what() << std::endl;
    }
    return 0;
}

소멸자에서의 예외 처리

 소멸자에서는 예외를 던지지 않아야 합니다. 그러나 예외가 발생할 수 있는 작업을 수행해야 한다면, 예외를 내부에서 처리해야 합니다.

#include <iostream>
 
class MyClass {
public:
    ~MyClass() noexcept {
        try {
            // 예외를 발생시킬 수 있는 작업
            throw std::runtime_error("Error in destructor");
        } catch (const std::exception& e) {
            // 예외 처리 (로깅 등)
            std::cerr << "Exception in destructor: " << e.what() << std::endl;
        }
    }
};
 
int main() {
    MyClass obj;
    return 0;
}

예외 명세 (Exception Specification)

 C++ 11 이전에는 함수가 던질 수 있는 예외를 명시하는 예외 명세를 사용했지만, 현재는 noexcept 지정자를 사용하여 함수가 예외를 던지지 않음을 나타냅니다.

void noThrowFunction() noexcept {
    // 이 함수는 예외를 던지지 않음
}
 
void mayThrowFunction() noexcept(false) {
    // 이 함수는 예외를 던질 수 있음
}

중첩 예외 (Nested Exceptions)

 C++ 11부터는 std::nested_exception을 사용하여 중첩된 예외를 처리할 수 있습니다.

#include <iostream>
#include <exception>
#include <stdexcept>
 
void nestedFunction() {
    try {
        throw std::runtime_error("Inner exception");
    } catch (const std::exception& e) {
        std::throw_with_nested(std::runtime_error("Outer exception"));
    }
}
 
void handleNestedExceptions() {
    try {
        nestedFunction();
    } catch (const std::exception& e) {
        std::cerr << e.what() << std::endl;
        try {
            std::rethrow_if_nested(e);
        } catch(const std::exception& ne) {
            std::cerr << "Nested exception: " << ne.what() << std::endl;
        }
    }
}
 
int main() {
    handleNestedExceptions();
    return 0;
}

실습 : 은행 계좌 시스템

 다음 요구사항을 만족하는 간단한 은행 계좌 시스템을 구현해보세요.

  1. InsufficientFundsExceptionInvalidAmountException 예외 클래스 정의
  2. BankAccount 클래스 구현 (입금, 출금, 잔액 조회 기능)
  3. 잔액 부족 시 InsufficientFundsException 발생
  4. 음수 금액 입력 시 InvalidAmountException 발생
#include <iostream>
#include <stdexcept>
#include <string>
 
class InsufficientFundsException : public std::runtime_error {
public:
    InsufficientFundsException(const std::string& msg) : std::runtime_error(msg) {}
};
 
class InvalidAmountException : public std::runtime_error {
public:
    InvalidAmountException(const std::string& msg) : std::runtime_error(msg) {}
};
 
class BankAccount {
private:
    double balance;
    std::string accountNumber;
 
public:
    BankAccount(const std::string& accNum, double initialBalance) 
        : accountNumber(accNum), balance(initialBalance) {
        if (initialBalance < 0) {
            throw InvalidAmountException("Initial balance cannot be negative");
        }
    }
 
    void deposit(double amount) {
        if (amount < 0) {
            throw InvalidAmountException("Deposit amount cannot be negative");
        }
        balance += amount;
    }
 
    void withdraw(double amount) {
        if (amount < 0) {
            throw InvalidAmountException("Withdrawal amount cannot be negative");
        }
        if (amount > balance) {
            throw InsufficientFundsException("Insufficient funds for withdrawal");
        }
        balance -= amount;
    }
 
    double getBalance() const { return balance; }
    std::string getAccountNumber() const { return accountNumber; }
};
 
int main() {
    try {
        BankAccount account("1234567890", 1000);
        
        std::cout << "Initial balance: " << account.getBalance() << std::endl;
 
        account.deposit(500);
        std::cout << "After deposit: " << account.getBalance() << std::endl;
 
        account.withdraw(200);
        std::cout << "After withdrawal: " << account.getBalance() << std::endl;
 
        account.withdraw(2000); // This should throw an exception
    } catch (const InsufficientFundsException& e) {
        std::cerr << "Transaction failed: " << e.what() << std::endl;
    } catch (const InvalidAmountException& e) {
        std::cerr << "Invalid operation: " << e.what() << std::endl;
    } catch (const std::exception& e) {
        std::cerr << "Unexpected error: " << e.what() << std::endl;
    }
 
    return 0;
}

연습 문제

  1. 다중 catch 블록과 예외 재전파를 사용하여 여러 단계의 함수 호출에서 예외를 처리하는 예제를 작성하세요.
  2. 중첩 예외를 사용하여 여러 단계의 함수 호출에서 발생한 예외 정보를 모두 캡처하고 출력하는 프로그램을 작성하세요.


참고 자료