티스토리 뷰

예외 처리

프로그램 실행 중에는 예기치 못한 오류가 발생될 일이 많이 생긴다. 이는 프로세스 내부만이 아닌 외부로 부터의 입/출력 혹은 제어 신호에 의해 발생할 수 도 있다.

 

 예로 파일을 열려할 때, 권한 문제로 거부를 당하거나, 함수에 잘못된 인수가 들어오고, 컨테이너 크기보다 더욱 방대한 데이터가 입력되는 경우를 예로 들 수 있다. 이런 경우 런타임 에러를 피하고 프로그램을 안전하게 종료시키기 위하여 올바른 정보로 재수행을 재시도하거나, 입력을 다시 받는 등의 적절한 예외 처리를 하여야 한다.

 

 


간단한 예외 처리

팩토리얼 오류 발생
#include <iostream>

using namespace std;

int fact(int n) {
	if (n == 1) return 1;
	return n * fact(n - 1);
)

int main() {
    int n;
    
    cout << "자연수를 입력하세요 : "
    ein >> n;
    
    if (n >= 1) { // n이 자연수일 때만 팩토리얼 계산
    	cout << n << "! = " << fact(n) << endl;
    )
    else { // 아니면 에러 메시지 출력
    	cout << n << ": 자연수가 아닙니다." << endl;
    }
}
자연수를 입력하세요 : 3
3! = 6
자연수를 입력하세요 : -1 // ERROR!!

 

위는 기본적인 팩토리얼 코드이다. 여기서 6번 줄에 "if (n == 1) return 1;" 조건문을 살펴보자.

 

6번 째 줄의 조건문은 팩토리얼 연산이 더이상 낮아질 수 없을 때, 재귀함수를 빠져나올 수 있는 조건문이다. 그런데, 만약 n이 0 이하의 수가 들어온다면 해당 조건문은 절대 참이될 수 없고 함수 스택은 무한히 쌓여 'StackOverFlow' 오류가 발생하게 된다.

 

이 같은 경우는 간단히 조건문을 수정하면 예외 처리가 가능해진다.

 

예외 처리
int fact(int n) {
	if (n < 2) return 1;
	return n * fact(n - 1);
)

 

 


try, catch, throw

C++는 try, catch, throw 키워드를 통해 예외 처리를 한다.

 

try문은 예외가 발생할 것 같은 부분을 중괄호에 넣는다. throw문은 try문의 중괄호 안에서 특정 오류가 발생할 때, 해당 오류에 대한 정보를 catch 문에 전달한다. 마지막으로 catch문은 예상되는 오류를 대응할 수 있는 코드를 작성하고 throw에서 전달된 오류 사항을 확인하여, catch문이 처리할 수 있는 오류인 경우, 이에 대응할 수 있는 코드를 실행하여 오류를 방지한다.

 

 

try문 내부의 코드는 반드시 실패한다는 가정이 아닌, 성공적으로 끝날 수 도 있고 오류가 발생할 수도 있는 상태이다. 즉, 코드에 오류가 발생하지 않는다면 catch문을 무시하게 된다. 하지만 하나의 오류라도 발생하면 바로 catch 문으로 이동한다.

 

 


표준 예외 처리 클래스

try 문에서 throw로 전달하는 메시지는 다양한 데이터형이 될 수 있다. 예로 throw를 int형이나 string형으로 전환할 수 있다.

 

#include <iostream>
#include <string>

using namespace std;

int main() {
    int n, r;
    try {
        cout << "자연수 2개 입력: ";
        cin >> n >> r;
        
        if (n < 0 || r < 0) throw to_string(n) + ", " + to_string(r);
        
        cout << n + r << endl;
    }
    catch (const string &e) {
        cout << "예외 처리 : " << e << endl;
    }

    return 0;
}
자연수 2개 입력: 0 -1
예외 처리 : 0, -1

 

위 코드는 int 형 변수를 string으로 변경하여 예외 처리로 전달되는 것이다. 이와 같은 방식으로 int형이나 기본 라이브러리에서 제공하는 표준 예외 처리 클래스를 사용할 수 있다.

 

 

표준 예외 처리 클래스
클래스 설명
exception 모든 표준 C++ 예외 클래스들의 기본 클래스, 모든 오류에 대해 동일한 오류 문구 출력
logic_error 논리 오류를 나타내는 예외 클래스, 잘못된 논리를 갖고 있을 때 발생하는 예외 처리
runtime_error 런타임 에러를 나타내는 예외 클래스, 일반적으로 프로그램 실행 중 발생하는 예외 처리 사용
out_of_range 범위를 벗어난 접근이 발생, 주로 배열이나 컨테이너에서 사용
invalid_argument 함수에 전달된 인수가 유효하지 않은 경우 발생하는 예외
bad_alloc 메모리 할당에 실패했을 때 발생
bad_cast dynamic_cast에서 잘못된 형 변환을 시도
bad_typeid typeid 연산자에서 유효하지 않은 type을 확인하려고 시도했을 때 발생
bad_exception 예외 처리 블록에서 예외를 잡지 않았을 때, 혹은 throw로 다시 던지지 않았을 때 발생하는 예외
system_error 시스템 호출에서 오류가 발생했을 때 발생하는 예외
ios_base::failure 입출력 작업에서 오류가 발생했을 때 발생하는 예외

 

https://tcpschool.com/cpp/cpp_exception_class

 

위 표는 표준 예외 클래스에서 자주 사용될 수 있는 클래스들이다. 필요한 예외 블록에서 예상가는 오류에 대한 대응을 하기 위해 위 클래스들을 적절히 사용하면 더욱 오류 사항을 확인하기 빠르고 편리해질 수 있다.

 

 


예외 처리 블록

try문에서 부터 우리는 중괄호를 통해 예외처리가 가능한 구역을 구별했다. 이러한 중괄호 구간을 예외 처리 블록이라고 명칭하며 예외 처리 블록의 위치에 따라 코드의 흐름이 변경될 수 있다.

 

아래 두 코드는 try 문의 블록의 위치가 다르다는 차이점이 있다. 이러한 차이점이 코드에 어떤 영향을 미칠까?

바로 반복문의 종료 시점이 달라진다는 것이다.

 

for문 외부 예외 처리
try {
	while (true) {
        int n, r;
        cout << "자연수 2개 입력: ";
        cin >> n >> r;

        if (cin.fail()) {
            throw string("입력이 잘못되었습니다.");
        }

        cout << comb(n, r) << endl;
    }
} catch (const string &e) {
    cout << e << endl;
    // 입력 스트림 초기화
    cin.clear();
    cin.ignore(numeric_limits<streamsize>::max(), '\n');
}

 

for문 외부에 예외 블록이 존재하는 경우, while문이 반복되는 중에 오류가 발생하면 try 블록 안의 모든 코드 실행을 취소하고 catch문으로 이동하여 반복문을 자연스럽게 나가게 된다.

 

for문 내부 예외 처리
while (true) {
    try {
        int n, r;
        cout << "자연수 2개 입력: ";
        cin >> n >> r;

        if (cin.fail()) {
            throw string("입력이 잘못되었습니다.");
        }

        cout << comb(n, r) << endl;
    }
    catch (const string &e) {
        cout << e << endl;
        // 입력 스트림 초기화
        cin.clear();
        cin.ignore(numeric_limits<streamsize>::max(), '\n');
    }
}

 

반대로, 예외 블록이 while문 안에 있는 경우, 예외 블록에서 오류가 발생하여 catch문으로 이동하더라도 catch문이 종료되면 다시 while 반복문이 실행된다.

 

이처럼 예외 처리는 단순한 예외만 처리하는 것이 아닌 코드의 흐름을 바꿀 수 있기도 하다.

 

 


다중 예외 처리

만약 발생할 수 있는 예외가 한 개 이상이라면?

 

앞서 우리는 예외 처리를 표준화되어 있는 모델을 사용하여 좀더 가독성이 좋게 하는 방법과 다양한 자료형으로 변환하여 다양하게 처리할 수 있는 방법을 알았다. 하지만, 하나의 예외 블록에서 한개 이상의 예외가 발생할 수 있다면 어떻게 할까?

 

물론 표준 예외 처리 클래스의 최상위 exception 클래스를 통해 모든 오류를 상세한 정보 없이 처리할 수 있지만, 발생한 예외를 알수도 없게 코드를 작성하고 싶은 사람은 없을 것이다. 하여 C++의 예외 처리는 다중 catch문을 선언할 수 있게 되어 있다.

 

#include <iostream>
#include <string>

using namespace std;

int main() {
    try {
        int n;
        cout << "숫자를 입력하세요: ";
        cin >> n;

        if (n == 1)
            throw 123; // int형 예외 던짐
        if (n == 2)
            throw string("abc"); // string형 예외 던짐
        if (n == 3)
            throw 3.14; // double형 예외 던짐, 런타임 에러
    }
    catch (int e) {
        cout << "int 예외: " << e << endl;
    }
    catch (const string &e) {
        cout << "string 예외: " << e << endl;
    }
    catch (...) {
        cout << "알수 없는 예외" << endl;
    }

    return 0;
}

 

위 코드에서 예외 처리 블록에서 int형과 string형을 던지는 예외처리가 발생한다. 이는 아래에 있는 2개의 catch문 중에서  throw문이 던지는 자료형을 받을 수 있는 catch문에게 전달된다. 만약, 위 코드에서 float형을 던진다면 어떠한 catch문에도 걸리지 않고 어떠한 예외 처리도 되지 않게된다.

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/05   »
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 31
글 보관함