C++에서의 예외 처리(Exception Handling)는 프로그램 실행 중 발생할 수 있는 비정상 상황을 안전하게 처리하기 위한 기법이다.
단순한 if문 조건 검사가 아닌 try-catch 구조를 통해 예외를 체계적으로 관리할 수 있다. 이번 장에서는 예외의 기본 개념부터 예외 클래스, 예외 상속, 예외 처리 흐름, 예외 던지기까지 차례대로 정리한다.
1. 예외(Exception)의 개념
- 정의
- 예외(Exception)란 프로그램 실행 도중 발생하는 문제 상황이다.
- 문법 오류(Syntax Error)는 예외가 아니다. (컴파일 단계에서 걸러짐)
- 예외는 프로그램의 논리와 맞지 않는 상황에서 발생한다.
- 예외는 throw로 발생시키고, catch에서 처리한다.
2. 예외 처리 방법
1) if문을 통한 단순 예외 처리
- 가장 단순한 예외 처리는 조건문을 사용하는 것이다.
- 그러나 if문만으로는 "예외 상황 처리"인지 "일반 흐름 제어"인지 명확히 구분되지 않는다.
💻 예제 코드
#include <iostream>
using namespace std;
int main() {
int n = 0;
if (n == 0) {
cout << "⚠️ 0으로 나눌 수 없습니다." << endl;
} else {
cout << 10 / n << endl;
}
return 0;
}
2) try-catch 구조
- try 블록: 예외가 발생할 가능성이 있는 코드 작성
- throw: 예외를 발생시키는 키워드
- catch: 발생한 예외를 처리하는 블록
⭕ 동작 순서
1️⃣ try 블록에서 예외 발생 시 → throw로 예외를 던진다.
2️⃣ 이어지는 catch 블록이 해당 예외를 받아 처리한다.
3️⃣ 예외 발생 시 try 블록의 남은 부분은 건너뛴다.
4️⃣ try는 하나지만 catch는 여러 개 둘 수 있다.
💻 예제 코드
#include <iostream>
using namespace std;
int main() {
try {
throw 10; // int 타입 예외 발생
}
catch (int n) { // int 타입 처리
cout << "예외 발생: 정수 " << n << endl;
}
catch (char ch) { // char 타입 처리
cout << "예외 발생: 문자 " << ch << endl;
}
return 0;
}
2-1) throw 키워드
- 예외를 발생시킬 때 사용하는 키워드이다.
- 예외 데이터를 던지고, 자료형이 일치하는 catch 블록에서 처리한다.
- 자료형이 다르면 예외가 전달되지만 처리되지 않고 상위 함수로 전달된다.
3. Stack Unwinding (스택 풀기)
- 예외가 발생했는데 현재 함수에서 처리하지 않으면 호출한 함수로 예외가 전달된다.
- 이를 스택 풀기(Stack Unwinding)라 한다.
- 스택의 특성(LIFO)에 따라 예외는 호출 순서의 역순으로 전파된다.
- main까지 처리되지 않으면 프로그램은 종료된다.
💻 예제 코드
#include <iostream>
using namespace std;
void inner() {
throw -1; // 예외 발생
}
void outer() {
inner(); // inner에서 예외 발생 → outer로 전파
}
int main() {
try {
outer(); // outer에서 예외 발생 → main으로 전파
}
catch (int n) {
cout << "스택 풀기 발생, 예외 값: " << n << endl;
}
return 0;
}
3-1) 예외 명시 (Exception Specification)
- 함수 선언부에서 어떤 종류의 예외가 발생할 수 있는지 명시할 수 있다.
- 함수의 특징 중 하나로 간주되어, 호출하는 쪽에서 예외 처리를 예측 가능하게 한다.
void 함수이름(매개변수) throw(자료형1, 자료형2, ...);
- throw(int, char) → 해당 함수는 int 또는 char 타입의 예외를 던질 수 있음을 의미한다.
- throw() → 아무 예외도 던지지 않음을 의미한다.
- 특정 자료형 예외 명시
#include <iostream>
using namespace std;
void throwFunc(int n) throw(int, char) {
if (n == 1) throw 123; // int 타입 예외
if (n == 2) throw 'A'; // char 타입 예외
}
int main() {
try {
throwFunc(2); // char 예외 발생
}
catch (int n) {
cout << "int 예외 처리: " << n << endl;
}
catch (char c) {
cout << "char 예외 처리: " << c << endl;
}
return 0;
}
· throw() 사용
#include <iostream>
using namespace std;
void throwFunc(int n) throw() {
throw 3.14; // double 예외 발생
}
int main() {
try {
throwFunc(1);
}
catch (...) {
cout << "이 블록은 실행되지 않는다." << endl;
}
return 0; // 프로그램 강제 종료
}
3-2) unexpected 함수
- 함수 선언에 명시되지 않은 예외가 던져질 경우 → unexpected() 함수가 호출된다.
- unexpected()의 기본 동작은 프로그램 종료이다.
- 필요하다면 set_unexpected()를 통해 다른 동작으로 바꿀 수 있다.
💻 예제 코드
#include <iostream>
#include <exception>
using namespace std;
void myUnexpected() {
cout << "❗ unexpected 함수 호출됨" << endl;
exit(1);
}
void throwFunc() throw(int) {
throw 'X'; // char 예외 발생 (명시되지 않은 예외)
}
int main() {
set_unexpected(myUnexpected); // unexpected 동작 재정의
throwFunc();
return 0;
}
실행 결과
❗ unexpected 함수 호출됨
3-3) 현대 C++에서의 주의점 ⚠️
- C++11 이후: 동적 예외 명시(throw(int, char))는 deprecated.
- C++17 이후: 완전히 제거됨.
- 현재는 noexcept 키워드를 사용한다.
4. 예외 클래스
- 예외 데이터는 클래스 객체일 수도 있다.
- 이러한 객체를 예외 객체(Exception Object)라고 한다.
- 예외 클래스는 최소한의 기능만 담아 정의하는 것이 좋다.
💻 예제 코드
#include <iostream>
using namespace std;
// 예외 클래스 정의
class nException {
int n;
public:
nException(int n) : n(n) {}
void showException() {
cout << "예외 발생: 값 " << n << endl;
}
};
void test(int n) {
if (n < 0) throw nException(n); // 음수일 경우 예외 발생
}
int main() {
try {
test(-5);
}
catch (nException &e) { // 객체 예외 처리
e.showException();
}
return 0;
}
5. 예외 클래스 상속
- 예외 클래스를 상속하면 다형성을 활용한 예외 처리가 가능하다.
- 단, catch 블록은 하위 클래스부터 작성해야 한다.
- 상위 클래스가 먼저 오면 모든 예외가 상위 클래스에서 처리되어 버린다.
💻 예제 코드
#include <iostream>
using namespace std;
// 기본 예외 클래스
class BaseException {
public:
virtual void show() { cout << "Base 예외" << endl; }
};
// 파생 예외 클래스 1
class DerivedException1 : public BaseException {
public:
void show() override { cout << "Derived1 예외" << endl; }
};
// 파생 예외 클래스 2
class DerivedException2 : public BaseException {
public:
void show() override { cout << "Derived2 예외" << endl; }
};
int main() {
try {
throw DerivedException2(); // Derived2 예외 발생
}
catch (DerivedException2 &e) { // 하위 클래스 먼저 처리
e.show();
}
catch (DerivedException1 &e) {
e.show();
}
catch (BaseException &e) {
e.show();
}
return 0;
}
6. 예외 던지기 (재던지기)
- catch 블록에서 예외를 처리한 뒤 다시 상위로 전달할 수 있다.
- throw;만 단독으로 사용하면 현재 예외를 그대로 던진다.
💻 예제 코드
#include <iostream>
using namespace std;
void func() {
try {
throw 100; // 예외 발생
}
catch (int n) {
cout << "func 내부에서 처리 후 재던지기" << endl;
throw; // 예외를 다시 던짐
}
}
int main() {
try {
func();
}
catch (int n) {
cout << "main에서 최종 처리: " << n << endl;
}
return 0;
}
7. new 연산자 예외
- 메모리 할당 실패 시 bad_alloc 예외가 발생한다.
- #include <new> 헤더에 정의되어 있다.
💻 예제 코드
#include <iostream>
#include <new>
using namespace std;
int main() {
try {
int* arr = new int[9999999999]; // 과도한 메모리 요청
}
catch (bad_alloc &e) {
cout << "메모리 할당 실패: " << e.what() << endl;
}
return 0;
}
8. 모든 예외 처리
- catch(...) 구문은 어떤 자료형이든 모든 예외를 처리한다.
- 단, 구체적인 예외 처리 후 마지막에 두는 것이 좋다.
try {
throw 3.14;
}
catch (...) {
cout << "모든 예외 처리" << endl;
}
✅ 마무리 정리
1️⃣ 예외(Exception)는 실행 중 발생하는 문제 상황이다.
2️⃣ try-catch 구조로 예외를 처리하며, throw로 예외를 발생시킨다.
3️⃣ throw 자료형과 catch 매개변수 자료형은 일치해야 한다.
4️⃣ 스택 풀기(Stack Unwinding)로 예외가 상위 함수로 전파된다.
5️⃣ 예외 클래스는 최소 기능만 정의하고, 상속 시 catch 순서에 주의한다.
6️⃣ catch 블록에서 예외를 다시 던질 수 있다.
7️⃣ new 연산자 실패 시 bad_alloc 예외가 발생한다.
8️⃣ catch(...)는 모든 예외를 처리한다.
'Language > C++' 카테고리의 다른 글
| 📌예제 - 은행 계좌 관리 시스템 (고급 구현) (0) | 2025.10.31 |
|---|---|
| 📌 Chapter 14 - 형변환 연산자 (C++ Casting Operators) (0) | 2025.10.30 |
| 📌 Chapter 12 - 클래스 템플릿 (0) | 2025.10.24 |
| 📌 Chapter 12 - 템플릿 (0) | 2025.10.24 |
| 📌 Chapter 11 - string (0) | 2025.10.11 |