예외 던지기 (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;
}
실습 : 은행 계좌 시스템
다음 요구사항을 만족하는 간단한 은행 계좌 시스템을 구현해보세요.
InsufficientFundsException
과InvalidAmountException
예외 클래스 정의BankAccount
클래스 구현 (입금, 출금, 잔액 조회 기능)- 잔액 부족 시
InsufficientFundsException
발생 - 음수 금액 입력 시
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;
}
연습 문제
- 다중
catch
블록과 예외 재전파를 사용하여 여러 단계의 함수 호출에서 예외를 처리하는 예제를 작성하세요. - 중첩 예외를 사용하여 여러 단계의 함수 호출에서 발생한 예외 정보를 모두 캡처하고 출력하는 프로그램을 작성하세요.
참고자료
- C++ 표준 문서의 예외 처리 섹션 : Exception handling
- throw 표현식 : throw expression
- try-catch 블록 : try-catch
- 표준 예외 클래스 : Standard exception classes
- noexcept 지정자 : noexcept specifier
- 중첩 예외 : Nested exceptions
- 예외 안전성에 대한 설명 : Exception safety