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. 중첩 예외를 사용하여 여러 단계의 함수 호출에서 발생한 예외 정보를 모두 캡처하고 출력하는 프로그램을 작성하세요.

 참고자료