C++ 예외 처리 완벽 가이드: try, catch, throw 완전 정복
예외(exception)는 프로그램 실행 중에 발생하는 문제입니다. C++ 예외는 프로그램이 실행되는 동안 0으로 나누기 시도 같은 예외적 상황이 발생했을 때의 대응입니다.
예외는 프로그램의 한 부분에서 다른 부분으로 제어를 전달하는 방법을 제공합니다. C++ 예외 처리는 세 키워드 try, catch, throw를 기반으로 합니다.
throw − 문제가 발생하면 프로그램이 예외를 던집니다. throw 키워드로 수행합니다.
catch − 프로그램이 문제 처리를 원하는 지점에서 예외 처리기로 예외를 잡습니다. catch 키워드가 예외 포착을 나타냅니다.
try − 특정 예외가 활성화될 코드 블록을 식별합니다. 하나 이상의 catch 블록이 뒤따릅니다.
블록이 예외를 발생시킬 것으로 가정하면, 메서드는 try와 catch 키워드 조합으로 예외를 잡습니다. try/catch 블록은 예외를 발생시킬 수 있는 코드 주변에 배치합니다. try/catch 블록 내 코드를 보호된 코드(protected code)라 하며, try/catch 사용 구문은 다음과 같습니다.
try {
// 보호된 코드
} catch(ExceptionName e1) {
// catch 블록
} catch(ExceptionName e2) {
// catch 블록
} catch(ExceptionName eN) {
// catch 블록
}
여러 catch 문을 나열해 try 블록이 다른 상황에서 둘 이상의 예외를 발생시킬 경우 다른 타입의 예외를 잡을 수 있습니다.
예외 던지기
예외는 throw 문을 사용해 코드 블록 어디서든 던질 수 있습니다. throw 문의 피연산자는 예외 타입을 결정하며, 어떤 표현식이든 될 수 있고 표현식 결과의 타입이 던져지는 예외 타입이 됩니다.
0으로 나누기 조건 발생 시 예외를 던지는 예제:
double division(int a, int b) {
if(b == 0) {
throw "Division by zero condition!";
}
return (a/b);
}
예외 잡기
try 블록 뒤의 catch 블록은 모든 예외를 잡습니다. catch 키워드 뒤 괄호에 나타나는 예외 선언에 의해 어떤 타입의 예외를 잡을지 결정됩니다.
try {
// 보호된 코드
} catch(ExceptionName e) {
// ExceptionName 예외 처리 코드
}
위 코드는 ExceptionName 타입의 예외를 잡습니다. try 블록에서 던져지는 어떤 타입의 예외든 처리하려면 예외 선언을 감싸는 괄호 사이에 줄임표 ...를 넣어야 합니다.
try {
// 보호된 코드
} catch(...) {
// 모든 예외 처리 코드
}
다음 예제는 0으로 나누기 예외를 던지고 catch 블록에서 잡습니다.
Live Demo
#include <iostream>
using namespace std;
double division(int a, int b) {
if(b == 0) {
throw "Division by zero condition!";
}
return (a/b);
}
int main() {
int x = 50;
int y = 0;
double z = 0;
try {
z = division(x, y);
cout << z << endl;
} catch(const char* msg) {
cerr << msg << endl;
}
return 0;
}
const char* 타입의 예외를 발생시키므로, 이 예외를 잡을 때 catch 블록에서 const char*를 사용해야 합니다. 컴파일하고 실행하면:
Division by zero condition!
C++ 표준 예외
C++은 <exception>에 정의된 표준 예외 목록을 제공하며, 이를 프로그램에서 사용할 수 있습니다. 이들은 부모-자식 클래스 계층으로 배열됩니다.
위 계층에 언급된 각 예외에 대한 간략한 설명:
| 번호 | 예외 및 설명 |
| 1 | std::exception 모든 표준 C++ 예외의 부모 클래스인 예외. |
| 2 | std::bad_alloc new에 의해 던져질 수 있음. |
| 3 | std::bad_cast dynamic_cast에 의해 던져질 수 있음. |
| 4 | std::bad_exception C++ 프로그램에서 예상치 못한 예외를 처리하는 유용한 장치. |
| 5 | std::bad_typeid typeid에 의해 던져질 수 있음. |
| 6 | std::logic_error 코드를 읽어 이론적으로 감지할 수 있는 예외. |
| 7 | std::domain_error 수학적으로 유효하지 않은 도메인이 사용될 때 던져지는 예외. |
| 8 | std::invalid_argument 잘못된 인자로 인해 던져짐. |
| 9 | std::length_error 너무 큰 std::string 생성 시 던져짐. |
| 10 | std::out_of_range 'at' 메서드에 의해 던져질 수 있음. 예: std::vector, std::bitset<>::operator[](). |
| 11 | std::runtime_error 코드를 읽어 이론적으로 감지할 수 없는 예외. |
| 12 | std::overflow_error 수학적 오버플로 발생 시 던져짐. |
| 13 | std::range_error 범위를 벗어난 값을 저장하려 할 때 발생. |
| 14 | std::underflow_error 수학적 언더플로 발생 시 던져짐. |
새 예외 정의
exception 클래스 기능을 상속하고 오버라이드해 자체 예외를 정의할 수 있습니다. 다음 예제는 std::exception 클래스를 사용해 표준 방식으로 자체 예외를 구현하는 방법을 보여줍니다.
Live Demo
#include <iostream>
#include <exception>
using namespace std;
struct MyException : public exception {
const char* what() const throw() {
return "C++ Exception";
}
};
int main() {
try {
throw MyException();
} catch(MyException& e) {
std::cout << "MyException caught" << std::endl;
std::cout << e.what() << std::endl;
} catch(std::exception& e) {
// 기타 에러
}
}
실행 결과:
MyException caught
C++ Exception
what()은 exception 클래스가 제공하는 public 메서드이며, 모든 자식 예외 클래스에서 오버라이드되었습니다. 이는 예외의 원인을 반환합니다.