학습 개요
- 프로그램이 동작하는 상황은 항상 정상적인 것은 아니며, 때로는 정상적인 동작을 할 수 없는 상황이 발생할 수 있음
- 이러한 경우를 고려하지 않고 프로그램을 작성하면 프로그램이 실행되는 동안 여러 가지 문제가 발생하여 비정상적인 에러가 발생하게 됨
- 그러므로 프로그램을 설계할 때는 정상적이지 않은 상황이 발생하는 것을 고려하여 이에 적절히 대처할 수 있도록 대비하는 것이 필요함
- 이러한 처리를 예외 처리라고 하며 C++에서는 예외를 처리하기 위한 문장과 클래스를 제공함
학습 목표
- 예외의 개념을 설명할 수 있음
- C++ 언어의 예외 처리 체계를 설명할 수 있음
- 예외 처리를 위한 구문을 사용하여 정상적이지 않은 상황에 대처할 수 있는 프로그램을 작성할 수 있음
- 예외 처리를 클래스를 이용하여 다양한 상황에 대한 예외의 발생 및 관련 데이터를 전달할 수 있는 프로그램을 작성할 수 있음
주요 용어
- 예외(
exception
)- 프로그램이 실행되는 도중에 발생할 수 있는 비정상적인 상황
- 예외 처리(exception handling)
- 프로그램 실행 중 예외가 발생하였을 때를 대비하여 마련해 놓은 처리 절차에 따라 대응하는 것
- 활성화 레코드(activation record)
- 함수가 호출되었을 때 복귀할 지점, 매개 변수, 지역 변수 등을 저장하는 데이터 집합
- 스마트 포인터(smart pointer)
- 자동적인 메모리 관리 기능 등이 추가된 포인터를 구현하는 추상 데이터 타입
- 예외 처리 클래스
- 예외 상황 발생 및 이에 대한 구체적 원인 등을 예외 처리 블록에 전달하기 위한 목적으로 선언한 클래스
강의록
예외의 개념
예외
- 예외(
exception
)란?- 프로그램 실행 도중에 발생할 수 있는 비정상적인 사건
- 비정상적인 데이터, 자원의 부족 등
- 예외 상황에 대한 적절한 대비를 하지 않으면 프로그램이 안정적으로 실행되지 않는 문제가 일어날 수 있음
- 프로그램 실행 도중에 발생할 수 있는 비정상적인 사건
- 예외 처리(exception handling)
- 프로그램 실행 중 예외가 발생하였을 때를 대비하여 마련해 놓은 처리 절차에 따라 대응하는 것
예외가 발생하는 상황의 예
- 정상적인 처리를 할 수 없는 데이터
기본 코드
1 2 3 4
double hmean(double a, double b) // 조화평균 { return 2.0 * a * b / (a + b); }
a == -b
인 경우 실행 중 프로그램 비정상 종료
exit()
사용1 2 3 4 5 6 7 8 9
double hmean(double a, double b) // 조화평균 { if (a == -b) { cout << "나누기를 할 수 없습니다." << endl; exit(EXIT_FAILURE); // 프로그램 강제 종료 } return 2.0 * a * b / (a + b); }
a == -b
인 경우 이를 알리고, 프로그램 강제 종료
- 프로그램이 요청하는 자원을 할당할 수 없는 경우
new(nothrow)
사용1 2 3 4
void f() { int *p = new(nothrow) int[100000000]; // 할당된 메모리의 활용 }
- 메모리 할당에 실패한 경우 실행 중 에러가 발생하여 프로그램이 비정상적으로 종료함
new(nothrow)
를 이용한 메모리 할당 오류 검사1 2 3 4 5 6 7
void f() { int *p = new(nothrow) int[100000000]; if (!p) { // p가 nullptr이면 cerr << "메모리 할당 오류" << endl; exit(EXIT_FAILURE); } }
- 메모리 할당이 이루어지지 않은 경우
new(nothrow)
는nullptr
를 반환하므로, 이를 이용하여 메모리 할당 오류를 검사함
- 메모리 할당이 이루어지지 않은 경우
C++ 언어의 예외 처리 체계
C++ 언어의 예외 처리 구문
try
블록,catch
블록, 그리고throw
문장으로 구성예외를 처리하는 함수
1 2 3 4 5 6 7 8 9 10 11 12
RetType1 someFunction() { try { // 예외가 발생할 수 있는 부분 someDangerousFunction(); } catch (eClass e) { // 발생한 예외를 처리하는 부분 exceptionProcRtn(); } }
예외를 던지는 함수
1 2 3 4 5 6 7
RetType2 someDangerousFunction() { if (예외검출조건) throw eObj; // 예외 발생을 알림 else // 정상적인 처리 }
예외 처리의 예 - HMean.cpp
HMean.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
#include <iostream> using namespace std; double hmean(double a, double b) // 조화평균 { if (a == -b) // 예외 검출 throw "조화평균을 계산할 수 없습니다!"; return 2.0 * a * b / (a + b); } int main() { double x, y, z; char cFlag = 'y'; while (cFlag != 'n') { cout << "\\n두 수를 입력하시오 : "; cin >> x >> y; try { // 예외 발생 가능성이 있는 코드 z = hmean(x, y); cout << "조화평균 = " << z << endl; } catch (const char* s) { // 예외 처리 cout << s << endl; } cout << "계속 할까요? (y/n) : "; cin >> cFlag; } return 0; }
예외 유형 별 처리
하나의
try
블록에 여러 개의catch
블록 사용1 2 3 4 5 6 7 8 9 10 11 12 13 14
try { // 예외가 발생할 가능성이 있는 함수 호출 } // throw된 객체의 자료형에 맞는 매개변수가 선언된 catch 블록에서 예외 처리함 catch (eClass1 e) { // 예외 처리 블록1 } catch (eClass2 e) { // 예외 처리 블록2 } catch (...) { // 그 외의 모든 예외 처리 } // 다음 문장
제어의 전달
정상적인 제어의 흐름
- 정상적인 호출 및 복귀 절차에 따라 실행됨
예외가 발생한 경우 제어의 흐름
- 발생된 예외를 처리하기 위한
catch
블록으로 함수 호출 스택을 따라 이동
- 발생된 예외를 처리하기 위한
예외 처리에 따른 자원 관리 문제
자원 소실이 가능한 상황
1 2 3 4 5 6 7 8 9 10 11
void f() { int *p = new int[1000]; for (int i = 0; i < 1000; i++) p[i] = i; if (ex_condition) throw "exception"; delete[] p; // throw 명령이 실행되면 나머지 문장들은 실행되지 않음 }
- 예외를 처리할
catch
블록으로 복귀할 때f()
가 호출될 때까지 거쳐온 함수들의 지역 변수들은 정상적인 소멸 과정을 거침
- 예외를 처리할
- 스마트 포인터의 활용
unique_ptr
- 할당된 메모리를 한 개의 포인터만 가리킬 수 있음
- 다른
unique_ptr
에 대입할 수 없으며, 이동 대입만 할 수 있음 unique_ptr
가 제거되거나nullptr
를 대입하면 가리키고 있던 메모리를 반납함
shared_ptr
- 할당된 메모리를 여러 개의 포인터로 가리킬 수 있음
- 다른
shared_ptr
에 대입 및 이동 대입 가능 - 포인터가 제거되거나
nullptr
를 대입하는 등의 처리로 그 메모리를 가리키는shared_ptr
이 더 이상 없으면 메모리를 반납함
스마트 포인터의 활용 예
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
#include <iostream> #include <memory> using namespace std; int main() { unique_ptr<int> p1{ new int }; unique_ptr<int> p2; *p1 = 10; cout << *p1 << endl; p2 = move(p1); // p2 = p1;은 불가 cout << *p2 << endl; p2 = nullptr; // 가리키고 있던 메모리는 해제됨 return 0; }
- 예외 처리로 인한 자원 소실 방지
unique_ptr
활용1 2 3 4 5 6 7 8 9 10 11
#include <memory> void f() { std::unique_ptr<int[]> p { new int[1000] }; for (int i = 0; i < 1000; i++) p[i] = i; if (ex_condition) throw "exception"; }
vector
활용1 2 3 4 5 6 7 8 9 10 11
#include <vector> void f() { std::vector<int> p(1000); for (int i = 0; i < 1000; i++) p[i] = i; if (ex_condition) throw "exception"; }
noexcept
지정자
noexcept
함수 지정- 함수가 예외를 일으키지 않음을 지정
1 2 3 4 5 6 7 8
template <typename T> T max(const vector<T>& v) noexcept { auto p = v.begin(); T m = *p; for (; p != v.end(); p++) if (m < *p) m = *p; return m; }
예외 처리 클래스
클래스에서 예외 처리 활용하기
- 예외 처리 클래스의 활용
- 클래스 설계 시 예외 처리 기능을 포함 시킴으로써 객체에서 예외가 발생하였을 때 그 위치나 원인 등의 식별을 용이하게 할 수 있음
- 클래스 선언문 내에 예외 처리 담당 클래스를 선언하여 활용함
클래스의 예외 처리 활용 예 - IntArray1.h
클래스 정의
1 2 3 4 5 6 7 8 9 10 11 12 13
const int DefaultSize = 10; class Array { int *buf; int size; public: Array(int s = DefaultSize); virtual ~Array() { delete[] buf; } int& operator[](/assets/img/knou/cpp/2025-07-02-knou-cpp-15/int offset); const int& operator[](/assets/img/knou/cpp/2025-07-02-knou-cpp-15/int offset) const; int getsize() const { return size; } friend ostream& operator<<(ostream&, Array&); class BadIndex {}; // exception class };
클래스의 예외 처리 활용 예 - IntArray1.cpp
생성자 및
operator[]
구현1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
#include "IntArray1.h" using namespace std; Array::Array(int s) : size(s) { buf = new int[s]; memset(buf, 0, sizeof(int) * s); } int& Array::operator[](/assets/img/knou/cpp/2025-07-02-knou-cpp-15/int offset) { if (offset < 0 || offset >= size) // 예외조건 검사 throw BadIndex(); // 예외객체 생성 및 전달 return buf[offset]; }
클래스의 예외 처리 활용 예 - IA1Main.cpp
메인 함수 및 예외 처리
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
#include <iostream> #include "IntArray1.h" using namespace std; int main() { Array arr(10); try { for (int i = 0; i < 10; i++) arr[i] = i; // arr[10] = 10; // 인덱스 범위를 벗어나는 접근 } catch (Array::BadIndex e) { cerr << "인덱스 범위 오류" << endl; } cout << arr << endl; return 0; }
예외 객체의 멤버를 통한 예외 정보 전달
IntArray1.h
1 2 3 4 5 6 7 8 9
class Array { public: class BadIndex { public: int wrongIndex; BadIndex(int n) : wrongIndex(n) {} }; };
IntArray1.cpp
1 2 3 4 5
int& Array::operator[](/assets/img/knou/cpp/2025-07-02-knou-cpp-15/int offset) { if (offset < 0 || offset >= size) throw BadIndex(offset); return buf[offset]; }
IA1Main.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14
int main() { // Array 객체 사용 // try { // } catch (Array::BadIndex e) { cout << "인덱스 범위 오류 --> " << e.wrongIndex << endl; } // }
exception
클래스
exception
클래스- C++ 언어에서 예외를 처리하기 위해 예외 처리 담당 클래스의 기초 클래스를 제공하는 클래스
- 헤더 파일
<exception>
을 소스 파일에 포함 시킴 - 가상 함수
what()
을 멤버 함수로 가지고 있음- 예외의 종류를
char*
형태로 반환함 exception
의 파생 클래스에서 재 정의하여 사용함
- 예외의 종류를
IntArray1.h 수정
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
#include <exception> class Array { public: class BadIndex : public exception { public: int wrongIndex; BadIndex(int n) : wrongIndex(n), exception() {} const char* what() const { return "Array Exception::"; } }; };
예외 객체의 다시 던지기
catch
블록에서 처리를 완결 할 수 없는 예외의 전달- 현 단계의
catch
블록에서 처리를 완결할 수 없는 예외에 대한 후속 처리를 할 수 있게 예외 객체를 다시throw
할 수 있음
- 현 단계의
1
2
3
4
5
6
7
8
9
class ExceptionClass
:public exception {
};
int f(int a) {
if (a < 0)
throw ExceptionClass();
}
1
2
3
4
5
6
7
8
9
10
int g(int x) {
try {
f(x);
}
catch (ExceptionClass e) {
// 현 단계의 예외 처리
throw; // 예외 객체를 다시 던지기
}
}
1
2
3
4
5
6
7
8
9
int h(int c) {
try {
g(c);
}
catch (ExceptionClass e) {
throw; // 후속 예외 처리
}
}
연습 문제
위 지문과 같이 함수가 정의되어 있다. condition이 false일 경우 함수
f()
에 의해 출력되는 내용은?1 2 3 4 5 6 7 8 9
class eCls { }; void g() { if (condition) throw 10; else throw eCls(); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
void f() { try { g(); } catch (char* s) { cout << "예외1 발생"; } catch (int e) { cout << "예외2 발생"; } catch (...) { cout << "예외3 발생"; } }
a. 예외3 발생
- condition이 false이면 함수
g()
는 eCls 객체를 전달함 - 함수
f()
의catch
블록은char*
,int
, 그외의 모든 예외 객체(‘…‘으로 표기함)에 대해 정의되어 있으며, eCls 객체는char*
도 아니고int
도 아니므로catch (...)
에 의해 처리 됨
- condition이 false이면 함수
다음 중 프로그램 실행 중 예외 발생하였음을 알리기 위해 사용하는 명령은?
a.
throw
- 예외가 발생할 가능성이 있는 곳에서는 이를 검사하여 예외 상황에 해당되는 경우
throw
명령으로 예외 객체를 던짐 - 이렇게 발생한 예외를 처리하는 구문은
try
블록과catch
블록으로 구성함 try
블록에는 예외가 발생할 가능성이 있는 처리를 하는 부분이며, 만일 예외 상황에 해당되는 경우throw
명령으로 예외 객체를 던짐- 이 예외는
catch
블록에서 선택적으로 받아 예외의 종류에 맞는 처리를 함
- 예외가 발생할 가능성이 있는 곳에서는 이를 검사하여 예외 상황에 해당되는 경우
함수를 선언할 때
noexcept
를 지정하는 것은 어떤 의미인가?a. 그 함수가 예외를 일으키지 않음을 지정함
noexcept
는 함수가 예외를 발생 시키지 않는다는 것을 지정하기 위한 키워드임- 이것은 예외가 발생되지 않을 것이 확실한 경우 컴파일러가 예외 처리 시퀀스를 고려할 필요가 없음을 알림으로써 컴파일러가 최적화를 하는 데 도움을 줌
위 지문 함수
f()
의 실행에 대한 설명으로 옳은 것은?1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
class A { }; void f( ) { int *p = new int[100]; A objA; if (ex_condition) throw "exception"; delete[ ] p; }
a.
ex_condition
이 참일 경우 할당된 메모리가 소실되는 문제가 있다.ex_condition
이 참이면throw
명령을 실행하게 되되, 이때 함수의 나머지 명령은 실행하지 않음- 따라서
new
연산자로 동적으로 할당되어 p가 가리키고 있는 메모리에 대한delete
연산이 실행되지 않으며, 결과적으로 할당된 메모리를 어느 것도 가리키고 있지 않아 메모리 소실이 일어남 - 그러나
throw
명령으로 함수에서 나가는 경우에도 지역 변수는 제거되며, 이 과정에서 객체의 소멸자가 동작함
정리 하기
- 예외란 프로그램이 실행되는 도중에 발생할 수 있는 비정상적인 사건을 의미함
try
블록은 예외가 발생할 가능성이 있는 문장을 포함함catch
블록은throw
명령을 통해 전달 된 예외 객체의 유형에 따라 적절한 예외 처리를 하는 문장을 포함함- 예외 상황이 발생되면
throw
명령으로 이를 알림 - 클래스 내에 예외 처리 담당 클래스를 선언하여 발생한 예외의 위치나 원인을 식별하게 할 수 있음
exception
클래스는 예외 처리 담당 클래스의 기초 클래스로서, 예외의 종류를char*
형태로 반환하는 가상함수what()
을 포함함