※ 예외처리(Exception Handling): 예외를 처리하는 과정

예외(Exception): 프로그램 실행도중 일어나는 비정상적인 상황

예를 들자면 아래와 같은 함수에서 분모에 0을 넣은 것과 동일한 상황이다.

좌) 정상적인 종료                                   우) 비정상적인 종료

우측 컴파일부분을 보면 Process finished with exit code -1073741676 (0xC0000094)라 적혀있다.

즉, 정상적이지 않은 값이 나와 오류가 발생했다는 뜻이다.

 

이런 경우, 프로그램이 오류가 발생했을 때, 컴파일러가 강제로 종료되거나 사전에 방지하기 위해 다음과 같은 방식을 사용한다.

 

- if문과 같은 조건문을 통한 예외처리

int fun(int a, int b){
    if (b == 0)
        exit(0);
    else
        return a / b;
}

int main() {
    int x, y;
    cin >> x >> y;
    cout << fun(x, y) << endl;
}

위와 같이 if문을 통한 예외를 제어할 수도 있다. 이는 C에서도 가능한 방식이다.

하지만 if-else문을 통한 에러처리방식에러가 발생한 객체에 대해 수명이 유지되어 에러를 처리하는동안에도 발생한 객체를 참조하는 코드가 정상적으로 컴파일된다.

따라서 C++을 포함한 여러 언어들에서는 다음과 같은 방식을 많이 사용한다.

 

 

- try catch를 통한 예외처리

try-catch는 if문의 예외처리와 달리 지역 객체들의 소멸자가 자동호출되어 메모리 누수현상을 조금 해결할 수 있다.

다만 try-catch 블록을 유지해야할 정보도 많고 실제 예외가 발생했을 때, 해줘야 할 일이 많아서 코드 크기나

예외 발생시 처리 속도는 전통적인 if 조건문 반환 값을 통한 오류처리와는 비교하기 힘들다.

try/catch는 자동으로 해주는 일이 많기에 당연히 더 느리다.

 

※ try-catch문을 통한 예외처리 매커니즘

 

class Animal {
public:
    Animal() { cout << "생성자" << endl; };
    ~Animal() { cout << "소멸자" << endl; };
};

int main(){
    try {
        Animal a;
        throw (a);
    }
    catch(Animal& a){
        cout << "catch문" << endl;
    }
}

/*출력문*/
//생성자
//소멸자
//catch문
//소멸자
다음과 같이 객체를 throw할 수 있는데, nested block 즉, try{ }가 종료가 되었으므로
Animal a를 통해 만들어진 객체 a는 소멸자에 의해 소멸하게 된다.
Q. 그렇다면 왜 출력문에서 catch문 뒤에 소멸자가 한 번 더 출력된 것일까?
A. 그건 바로 catch(Animal& a)에서 생성자가 한 번 더 생성되었다는 것의 반증이 될 것이다.
생성자는 처음 객체생성되었을 때, 초기화를 위해 딱 한번만 호출된다는 것이 기본 개념이고, 핵심이기 때문이다.
따라서 catch 블록이 종료된 이후 Animal& a를 통해 생성된 객체를 다시 소멸자가 소멸해줘서 소멸자가 두번 출력된 것!

 

 

 

 

 

Exception 클래스 (예외 클래스)

Exception 클래스:  예외처리를 위해 exception 헤더에서 제공하는 클래스 (표준 라이브러리인 std namespace에 존재)

이런 예외클래스도 클래스이기에 상속은 물론 다형성, 생성자와 연산자에도 적용 가능하다.

 

§ what() 

what은 exception 클래스에서 하나의 문자열 포인터를 반환하는 가상멤버함수이다.

what()은 exception 클래스에서는 아무 의미가 없지만 파생클래스에서 원하는 문자열을 출력할 수 있게 재정의 해준다!

 

#include <iostream>
#include <exception>
#include <string>
using namespace std;

class NewException : public exception{ //새로운 Exception NewException은 exception클래스를 상속받음
public:
    const char* what() const noexcept override{ // what 함수의 오버라이딩 진행
        return "NewException";
    }
};

int main(){
    try{
        string str;
        str.resize(-100);
    }
    catch (exception& e){
        try {
            throw NewException(); // 예외 발생시 새로운 Exception throw
        }
        catch (const NewException& newException){
            cout << "My exception is " << newException.what() << endl; // NewException의 what()에서 전달받은 문자열 출력
        }
    }
}

 

 

 

 

 

§ 표준 Exception 클래스

#include <exception>는 exception 클래스로부터 파생된 다양한 표준 exception 클래스를 정의하고 있다.

[가장 기초클래스가 되는 2개의 클래스, logic과 runtime]

logic_error 클래스는 일반적인 논리에 관한 오류들을 처리

runtime_error 클래스프로그램 실행하는 동안 발생할 수 있는 다양한 오류들을 처리

출처) http://www.tcpschool.com/cpp/cpp_exception_class

 

 

 

 

 

 

※ Custom Exception (예외클래스의 설계)

예외발생을 알리기 위한 예외 객체의 생성을 위해 정의된 클래스로 기본 자료형 데이터 뿐만 아니라

클래스의 객체도.예외데이터가 될 수 있다. 또한 예외클래스도 상속의 관계를 구성할 수 있다

 

 

 

 

 

※ 예외클래스 전달방식의 주의사항

try 뒤를 이어 등장하는 catch가 2개 이상일 때, 다음과 같은 과정을 거친다.

 

윤성우의 열혈C++

 

int throwExceptions (int c) {
    if (c == 1){
        throw string("It's string!");
    }
    if (c == 2){
        throw 2;
    }
    if (c == 3){
        throw "hello world!";
    }
    if (c == 4){
        throw 'X';
    }
    return 0;
}

int main(){
    int c;
    while (true) {
        cin >> c;
        try {
            throwExceptions(c);
        }
        catch (string& s){
            cout << "string exception: " << s << endl;
        }
        catch (const char* s) {
            cout << "string literal exception: " << s << endl;
        }
        catch (char x) {
            cout << "char exception: " << x << endl;
        }
        catch (int x) {
            cout << "int exception: " << x << endl;
        }
    }
}

/*---------------출력문---------------*/
//1
//string exception: It's string!
//2
//int exception: 2
//3
//string literal exception: hello world!
//4
//char exception: X

 

 

 

 

 

 

※ noexcept (true)   (feat. move constructor)

예외가 생성되지 않음을 뜻하는 키워드

호출자가 noexcept 여부에 의존할 수 있으며 예외를 방출하지 않을 함수는 noexcept로 선언한다.

 

[장점]

1. user와 compiler에게 힌트가 됨.

2. code의 단순화

3. compiler의 최적화를 가능하게 함.

 

int funcA() noexcept {
    throw std::runtime_error("Exception!");
    return 0;
}

int main(){
    try { 
        funcA();
    } 
    catch (char c){
        cout << "catch char: " << c;
    }
}

 

 

※ 이동생성자와 noexcept

[이동생성자 사용 시 주의할 점]

1. 이동생성자에는 noexcept 키워드가 지정되어야 한다. (이동생성자 수행중, 예외가 없다는 것을 컴파일러가 인지해야함)

2. shallow copy가 일어나는 변수(포인터, 주소관련 변수)에 nullptr를 넣어줘야 함

3, 소멸자에서 메모리할당 관련 변수가 nullptr인지 확인해 줘야함.

Animal(Animal && a) noexcept { //이동생성자에 의한 얕은복사
    age = a.age;
    name = a.name;
    cout << "이동생성자" << endl;
    a.name = nullptr;
}

 

 

 

 

 

 

 

 

 

 

 

※ rethrowing

- throw를 다시 하여 예외를 부분적으로 다루기(handling) 위해서 사용.

int funcA() {
    throw std::runtime_error("Exception!");
    return 0;
}
int funcB() {
    try {
        funcA();
    }
    catch (std::runtime_error& e) {
        cout << "catch Exception: " << e.what() << endl;
        throw;
    }
}

int main(){
    try {
        funcB();
    }
    catch (std::runtime_error& e) {
        cout << "catch Rethrowed exception: " << e.what() << endl;
    }
}

/*---------------출력문---------------*/
//catch Exception: Exception!
//catch Rethrowed exception: Exception!

+ Recent posts