----------------
title : 씹어먹는 C ++ - <11. C++ 에서 예외 처리>
path : /C++
cat_title : 11. C++ 에서 예외 처리
publish_date : 2018-09-17
chapter : C++ 에서의 예외 처리
next_page : 227
--------------

이번 강좌에서는
* C++ 에서의 예외 처리
* `throw, try, catch`

![](/img/ChewingCpplogo.png)

안녕하세요 여러분! 오래 간만에 인사 드립니다. 이번 강좌에서는 C++ 에서 예외 처리를 어떠한 방식으로 하는지에 대해 알아보도록 하겠습니다.

### 예외란?

우리가 이상적인 세상에서 살고 있다면, 그 어떤 예외적인 상황도 없을 것입니다. 프로그램 혹은 라이브러리 사용자들은 언제나 올바른 값을 입력값으로 줄 것이고, 컴퓨터 역시 무한한 자원을 사용할 수 있어서 어떠한 상황에서도 데이터들을 정상적으로 처리할 수 있을 것입니다.

하지만, 안타깝게도 이 세상은 그리 녹록하지 않습니다. 사람들은 실수를 하기 마련이고, 컴퓨터 역시 언제나 프로그램에 필요한 자원을 제공할 수 있는 것이 아닙니다. 예를 들어서 아래와 같은 `vector` 의 사용 예시를 살펴봅시다.

```cpp-formatted
std::vector<int> v(3);  // 크기가 3 인 벡터 만듦
std::cout << v.at(4);   // ??
```

위 경우, 크기가 3 인 `vector` 를 만들었지만 4 번째 원소를 요청하고 있습니다. 위와 같은 코드는 문법 상 아무 문제가 없는 코드이지만, 막상 실행하게 되면 오류가 발생하게 됩니다.

다른 예로 아래와 같이 큰 메모리를 할당하는 경우를 생각해봅시다.

```cpp-formatted
std::vector<int> v(1000000000);
// ?
```

여러분이 사용하는 대부분의 시스템의 경우 위와 같이 큰 메모리를 할당할 수 없습니다. 따라서, 위 코드 역시 문법 상 틀린 것이 없는 코드 이지만, 실제로 실행해보면 오류가 발생하게 됩니다. 

이렇게 정상적인 상황에서 벗어난 모든 예외적인 상황들을 **예외(exception)** 이라고 부릅니다. 

### 기존의 예외 처리 방식

C 언어에서는 언어 차원에서 제공하는 예외 처리 방식이라는 것이 딱히 따로 존재하지 않았습니다. 따라서 아래와 같이, 어떤 작업을 실행한 뒤에 그 결과값을 확인하는 방식으로 처리하였습니다. 예를 들어서 아래 `malloc` 으로 메모리를 동적으로 할당하는 경우를 생각해봅시다.

```cpp-formatted
char *c = (char *)malloc(1000000000);
if (c == NULL) {
  printf("메모리 할당 오류!");
  return;
}
```

`malloc` 의 경우 메모리 할당 실패시에 `NULL` 을 리턴하므로, 위와 같이 `c` 가 `NULL` 인지 확인함으로써 예외적인 상황을 처리할 수 있었습니다.

하지만 이러한 방식으로 예외를 처리한다면, 함수가 깊어지면 깊어질 수 록 꽤나 귀찮게 됩니다. 예를 들어서 아래와 같은 예시를 살펴보세요.

```cpp-formatted
bool func1(int *addr) {
  if (func2(addr)) {
    // Do something
  }
  return false;
}
bool func2(int *addr) {
  if (func3(addr)) {
    // Do something
  }
  return false;
}
bool func3(int *addr) {
  addr = (int *)malloc(100000000);
  if (addr == NULL) return false;
  return true;
}
int main() {
  int *addr;
  if (func1(addr)) {
    // 잘 처리됨
  } else {
    // 오류 발생
  }
}
```

위 코드의 경우 `func3` 에서 '예외가 발생할 수 있는 작업' 을 수행하는데, 만약에 예외가 발생하게 된다면 `false` 를 리턴하게 되고, 잘 처리 되었다면 `true` 를 리턴합니다. 

여기까지는 좋은데, 문제는 이 `func3` 가 `func2` 에서 호출되고, 다시 `func2` 는 `func1` 에서 호출되고, `func1` 은 `main` 에서 호출된다는 점입니다. 만약에 `main` 의 입장에서 `func3` 에서 문제가 발생했을 때 이를 캐치하기 위해서는, 각각의 함수들에서 처리 결과를 모두 리턴해야 할 것입니다.

위 코드는 예외가 `func3` 에서만 발생해서 간단하였지만, 만약에 `func2` 도 어떤 다른 작업을 해서 예외를 발생시킬 수 있다면 어떻게 해야 할까요? 상당히 골치 아픈 일입니다.

하지만 다행이도 C++ 에서는 위와 같은 불편한 예외 처리 방식을 획기적으로 해결시켰습니다.

### 예외 발생시키기 - throw

C 언어에서는 예외가 발생했을 때, 다른 값을 리턴하는 것으로 예외를 처리하였지만, C++ 에서는 예외가 발생하였다는 사실을 명시적으로 나타낼 수 있습니다. 바로 `throw` 문을 사용하면 됩니다.

예를 들어서 아래와 같이 매우 간단한 `vector` 클래스를 생각해봅시다.

```cpp-formatted
template <typename T>
class Vector {
 public:
  Vector(size_t size) : size_(size) {
    data_ = new T[size_];
    for (int i = 0; i < size_; i++) {
      data_[i] = 3;
    }
  }
  const T& at(size_t index) const {
    if (index >= size_) {
      throw out_of_range("vector 의 index 가 범위를 초과하였습니다.");
    }
    return data_[index];
  }
  ~Vector() { delete[] data_; }

 private:
  T* data_;
  size_t size_;
};
```

만들어진 `vector` 의 요청한 위치에 있는 원소를 리턴하는 함수인 `at` 함수를 생각해봅시다\sidenote{at 함수는 operator[] 와 같이 index 로 전달된 위치에 있는 원소를 리턴합니다. 하지만 차이점으로, at 의 경우 const 객체를 리턴해서, 이를 변경할 수 없습니다.} 인자로 전달된 `index` 가 범위 이내라면, 간단하게 `data[index]` 를 리턴하면 되겠지만, 범위 밖이라면 어떻게 해야 할까요?

문제는 `at` 함수가 `const T&` 를 리턴하기 때문에, 따로 '오류 메세지' 를 리턴할 수 없다는 점입니다. 하지만 C++ 에서는 다음과 같이 예외가 발생하였음을 명시적으로 알릴 수 있습니다.

```cpp-formatted
// 생략 ...
const T& at(size_t index) const {
  if (index >= size) {
    // 예외를 발생시킨다!
    throw std::out_of_range("vector 의 index 가 범위를 초과하였습니다.");
  }
  return data[index];
}
// 생략 ...
}
;
```

먼저, 예외를 발생시키는 부분을 자세히 살펴보겠습니다. 

```cpp-formatted
throw std::out_of_range("vector 의 index 가 범위를 초과하였습니다.");
```

C++ 에는 예외를 던지고 싶다면, **`throw` 로 예외로 전달하고 싶은 객체를 써주면 됩니다.** 예외로 아무 객체나 던져도 상관 없지만, C++ 표준 라이브러리에는 이미 여러가지 종류의 예외들이 정의되어 있어서 이를 활용하는 것도 좋습니다. 예를 들어서, 위 경우 `out_of_range` 객체를 `throw` 합니다. C++ 표준에는 `out_of_range` 외에도 `overflow_error, length_error, runtime_error` 등등 여러가지가 정의되어 있고 표준 라이브러리에서 활용되고 있습니다.

이렇게 예외를 `throw` 하게 되면, `throw` 한 위치에서 즉시 함수가 종료되고, 예외 처리하는 부분까지 점프하게 됩니다. 따라서 `throw` 밑에 있는 모든 문장은 실행되지 않습니다. 한 가지 중요한 점은 이렇게 함수에서 예외 처리하는 부분에 도달하기 까지 함수를 빠져나가면서, **`stack` 에 생성되었던 객체들을 빠짐없이 소멸**시켜 준다는 점 입니다. 따라서 예외가 발생하여도 사용하고 있는 자원들을 제대로 소멸시킬 수 있습니다! (소멸자만 제대로 작성하였다면)

### 예외 처리 하기 - try 와 catch

그렇다면 이렇게 발생한 에외를 어떻게 처리할까요?

```cpp-formatted
#include <iostream>
#include <stdexcept>

template <typename T>
class Vector {
 public:
  Vector(size_t size) : size_(size) {
    data_ = new T[size_];
    for (int i = 0; i < size_; i++) {
      data_[i] = 3;
    }
  }
  const T& at(size_t index) const {
    if (index >= size_) {
      throw std::out_of_range("vector 의 index 가 범위를 초과하였습니다.");
    }
    return data_[index];
  }
  ~Vector() { delete[] data_; }

 private:
  T* data_;
  size_t size_;
};
int main() {
  Vector<int> vec(3);

  int index, data = 0;
  std::cin >> index;

  try {
    data = vec.at(index);
  } catch (std::out_of_range& e) {
    std::cout << "예외 발생 ! " << e.what() << std::endl;
  }
  // 예외가 발생하지 않았다면 3을 이 출력되고, 예외가 발생하였다면 원래 data 에
  // 들어가 있던 0 이 출력된다.
  std::cout << "읽은 데이터 : " << data << std::endl;
}
```

성공적으로 컴파일 하였다면

![1 입력 시에 읽은 데이터로 3 이 나오고, 4 입력 시에 예외가 발생하며 읽은 데이터로 0 이 나온다.](/img/12.1.1.png)

와 같이 나옵니다. 

위에서도 볼 수 있듯이, 범위에 벗어난 값 (위 경우 3 이상) 을 입력하게 되었다면, 범위를 초과하였다는 메세지를 볼 수 있습니다. 그렇다면, 예외가 어떤 식으로 처리되었는지 살펴봅시다. 

```cpp-formatted
try {
  data = vec.at(index);
}
```

먼저 `try` 부분 입니다. `try` 안에서 무언가 예외가 발생할만한 코드가 실행 됩니다. 만약에 예외가 발생하지 않았다면 마지 `try `..` catch` 부분이 없는 것과 동일하게 실행 됩니다. `data` 에는 `vec` 의 `index` 번째 값이 들어가고 밑에 있는 `catch` 문은 무시 됩니다. 

반면에 예외가 발생할 경우 이야기가 달라집니다. 예외가 발생하게되면, 그 즉시 `stack` 에 생성된 모든 객체들의 소멸자들이 호출되고, 가장 가까운 `catch` 문으로 점프합니다. 따라서, 위 경우 

```cpp-formatted
if (index >= size_) {
  throw std::out_of_range("vector 의 index 가 범위를 초과하였습니다.");
}
```

의 `throw` 다음으로 실행되는 문장이 바로 

```cpp-formatted
catch (std::out_of_range& e) {
  std::cout << "예외 발생 ! " << e.what() << std::endl;
}
```

이 `catch` 부분이 됩니다. 여기서 `catch` 문은 `throw` 된 예외를 받는 부분인데, 어떤 예외를 받냐면, `catch` 문 안에 정의된 예외의 꼴에 맞는 객체를 받게 됩니다. 우리의 `Vector` 의 경우 `out_of_range` 를 `throw` 하였는데, 위 `catch` 문이 `out_of_range` 를 받으므로, 잘 받을 수 있습니다. 

`out_of_range` 클래스는 아주 간단한데, 그냥 내부에 발생엔 예외에 관한 내용을 저장하는 문자열 필드가 달랑 하나 있고 이 역시 `what()` 함수로 그 값을 들여다 볼 수 있습니다. 위 경우 우리가 전달한 문장인 '`vector`의 `index`가 범위를 초과하였습니다' 가 나오게 됩니다. 

### 스택 풀기 (stack unwinding)

앞서 `throw` 를 하게 된다면, 가장 가까운 `catch` 로 점프한다고 하였습니다. 이 말의 뜻이 무엇인지 아래 예제로 살펴봅시다.

```cpp-formatted
#include <iostream>
#include <stdexcept>

class Resource {
 public:
  Resource(int id) : id_(id) {}
  ~Resource() { std::cout << "리소스 해제 : " << id_ << std::endl; }

 private:
  int id_;
};

int func3() {
  Resource r(3);
  throw std::runtime_error("Exception from 3!\n");
}
int func2() {
  Resource r(2);
  func3();
  std::cout << "실행 안됨!" << std::endl;
  return 0;
}
int func1() {
  Resource r(1);
  func2();
  std::cout << "실행 안됨!" << std::endl;
  return 0;
}

int main() {
  try {
    func1();
  } catch (std::exception& e) {
    std::cout << "Exception : " << e.what();
  }
}
```

성공적으로 실행하였으면

```exec
리소스 해제 : 3
리소스 해제 : 2
리소스 해제 : 1
Exception : Exception from 3!
```

와 같이 나옵니다. 

먼저 살펴보아야할 부분으로, 

```cpp-formatted
int func3() {
  Resource r(3);
  throw std::runtime_error("Exception from 3!\n");
}
```

에서 보시다시피, `func3` 함수에서 예외를 발생시키고 있습니다. 그런데, 이 `func3` 은 `func2` 가 호출하고, `func2` 는 `func1` 이 호출하고, 마지막으로 `func1` 은 `main` 에서 호출됩니다.

앞에서 말했듯이 예외가 발생하게 되면 가장 가까운 `catch` 에서 예외를 받는다고 하였습니다. 그런데, `func1, 2` 모두 예외를 받는 `catch` 구문이 없습니다. 따라서, 가장 가까운 `catch` 부분은, `main` 함수에 있는 `catch` 구문이 되고, 실제로도 예외가 `main` 함수에까지 잘 전달되어서 출력되었습니다.

또 한 가지 중요한 점은, 예외가 전파되면서 각 함수들에 정의되어 있던 객체들이 잘 소멸되었다는 점입니다. 

먄약에 예외가 발생하지 않았을 경우 어떻게 나오게 되냐면

```cpp-formatted
#include <iostream>
#include <stdexcept>

class Resource {
 public:
  Resource(int id) : id_(id) {}
  ~Resource() { std::cout << "리소스 해제 : " << id_ << std::endl; }

 private:
  int id_;
};

int func3() {
  Resource r(3);
  return 0;
}
int func2() {
  Resource r(2);
  func3();
  std::cout << "실행!" << std::endl;
  return 0;
}
int func1() {
  Resource r(1);
  func2();
  std::cout << "실행!" << std::endl;
  return 0;
}

int main() {
  try {
    func1();
  } catch (std::exception& e) {
    std::cout << "Exception : " << e.what();
  }
}
```

성공적으로 컴파일 하였다면

```exec
리소스 해제 : 3
실행!
리소스 해제 : 2
실행!
리소스 해제 : 1
```

와 같이 나옵니다.

위와 비교해보면, 정상적인 상황에서는 객체의 소멸자들은 함수가 종료될 때 호출되므로 "실행!" 이 먼저 출력되고, 그 뒤에 리소스 해제 되었다는 문장이 출력됩니다. 

반면에 예외가 전파되는 과정에서는 바로 `catch` 부분으로 점프 하면서, 각 함수들에 있던 객체들만 해제하기 때문에 리소스 해제 되었다는 것은 정상적으로 출력되지만, 그 "실행 안됨!" 부분은 실행되지 않습니다. 

이와 같이 `catch` 로 점프 하면서 스택 상에서 정의된 객체들을 소멸시키는 과정을 **스택 풀기(stack unwinding)** 이라고 부릅니다. 

```lec-warning
예외를 생성자에서 던질 때 주의해야 할 점이 하나 있습니다. 바로 **생성자에서 예외가 발생 시에 소멸자가 호출되지 않는다** 라는 점입니다. 따라서, 만일 예외를 던지기 이전에 획득한 자원이 있다면 `catch` 에서 잘 해제시켜 줘야만 합니다.
```

### 여러 종류의 예외 받기

앞서 `catch` 는 여러 종류의 예외들을 받을 수 있다고 하였습니다. 이를 위해선, 한 개의 `try` 안에 받고자 하는 모든 종류의 예외를 `catch` 문으로 주렁 주렁 달면 됩니다. 아래 예제를 보실까요.

```cpp-formatted
#include <iostream>
#include <string>

int func(int c) {
  if (c == 1) {
    throw 10;
  } else if (c == 2) {
    throw std::string("hi!");
  } else if (c == 3) {
    throw 'a';
  } else if (c == 4) {
    throw "hello!";
  }
  return 0;
}

int main() {
  int c;
  std::cin >> c;

  try {
    func(c);
  } catch (char x) {
    std::cout << "Char : " << x << std::endl;
  } catch (int x) {
    std::cout << "Int : " << x << std::endl;
  } catch (std::string& s) {
    std::cout << "String : " << s << std::endl;
  } catch (const char* s) {
    std::cout << "String Literal : " << s << std::endl;
  }
}
```

성공적으로 컴파일 하였다면 

![리소스 잘 해제되고 실행도 잘 됨](/img/12.1.4.png)

와 같이 나옵니다.

마치 `switch` 문 처럼 `catch` 역시 여러 종류의 `throw` 된 객체를 모두 받을 수 있습니다. 위 경우, 

```cpp-formatted
catch (char x) {
  std::cout << "Char : " << x << std::endl;
}
catch (int x) {
  std::cout << "Int : " << x << std::endl;
}
catch (std::string& s) {
  std::cout << "String : " << s << std::endl;
}
catch (const char* s) {
  std::cout << "String Literal : " << s << std::endl;
}
```

첫번째 `catch` 문에서는 `char` 형 값을, 두 번째에서는 `int` 형 값을, 세 번째 에서는 `string` 객체를, 마지막에서는 `const char*` 형 값을 받게 됩니다. 실제로도 각기 다른 값들을 `throw` 하였을 때, 작동하는 `catch` 가 달라지는 것을 확인할 수 있습니다.

또한 한 가지 흥미로운 점은, 기반 클래스와 파생 클래스의 경우 처리하는 방식입니다. 

```cpp-formatted
#include <exception>
#include <iostream>

class Parent : public std::exception {
 public:
  virtual const char* what() const noexcept override { return "Parent!\n"; }
};

class Child : public Parent {
 public:
  const char* what() const noexcept override { return "Child!\n"; }
};

int func(int c) {
  if (c == 1) {
    throw Parent();
  } else if (c == 2) {
    throw Child();
  }
  return 0;
}

int main() {
  int c;
  std::cin >> c;

  try {
    func(c);
  } catch (Parent& p) {
    std::cout << "Parent Catch!" << std::endl;
    std::cout << p.what();
  } catch (Child& c) {
    std::cout << "Child Catch!" << std::endl;
    std::cout << c.what();
  }
}
```

성공적으로 컴파일 하였다면

![Child 도 Parent 에서 catch 됨](/img/12.1.5.png)

와 같이 나옵니다. 

이번에는 경우에 따라서 `Parent` 나 `Child` 클래스 객체를 리턴합니다. `Parent` 클래스 객체를 `throw` 하였을 때에는 예상했던데로 `Parent` 를 받는 `catch` 문이 실행되어서 *"Parent Catch!"* 가 출력되었습니다.

반면에 `Child` 객체를 `throw` 하였을 때에는 예상과는 다르게, `Child` 를 받는 `catch` 문이 아닌, `Parent` 를 받는 `catch` 문이 실행되어서 이 역시 *"Parent Catch!"* 가 출력되었습니다.

이와 같은 일이 발생한 이유는, `catch` 문의 경우 가장 먼저 대입될 수 있는 객체를 받는데;

```cpp-formatted
Parent& p = Child();
```

는 가능하기 때문에 `Parent catch` 가 먼저 받아버리는 것입니다. 따라서, 위와 같은 문제를 방지하기 위해서는 언제나 `Parent catch` 를 `Child catch` 보다 뒤에 써주는 것이 좋습니다. 왜냐하면 이를 통해서 `Child` 객체가 `Parent catch` 에 들어가는 것을 막을 수 있고,

```cpp-formatted
Child &c = Parent();  // 오류
```

위는 성립되지 않기 때문에 `Child catch` 에 `Parent` 객체가 들어가지도 않습니다. 실제로 예를 보면;

```cpp-formatted
#include <exception>
#include <iostream>

class Parent : public std::exception {
 public:
 
  // what 은 std::exception 에 정의된 함수로, 이 예외가 무엇인지 설명하는 문자열을
  // 리턴하는 함수 입니다.
  virtual const char* what() const noexcept override { return "Parent!\n"; }
};

class Child : public Parent {
 public:
  const char* what() const noexcept override { return "Child!\n"; }
};

int func(int c) {
  if (c == 1) {
    throw Parent();
  } else if (c == 2) {
    throw Child();
  }
  return 0;
}

int main() {
  int c;
  std::cin >> c;

  try {
    func(c);
  } catch (Child& c) {
    std::cout << "Child Catch!" << std::endl;
    std::cout << c.what();
  } catch (Parent& p) {
    std::cout << "Parent Catch!" << std::endl;
    std::cout << p.what();
  }
}
```

성공적으로 컴파일 하였다면

![Child 가 제대로 처리됨](/img/12.1.6.png)

와 같이 잘 처리됨을 알 수 있습니다.

```lec-warning
일반적으로 예외 객체는 `std::exception` 을 상속 받는 것이 좋습니다. 왜냐하면 표준 라이브러리의 유용한 함수들(`nested_exception` 등) 을 사용할 수 있기 때문이지요. 
```

### 모든 예외 받기

만약에 어떤 예외를 `throw` 하였는데, 이를 받는 `catch` 가 없다면 어떻게 될까요?

```cpp-formatted
#include <iostream>
#include <stdexcept>

int func() { throw std::runtime_error("error"); }

int main() {
  try {
    func();
  } catch (int i) {
    std::cout << "Catch int : " << i;
  }
}
```

성공적으로 컴파일 하였다면

![프로그램 비정상적으로 종료!](/img/12.1.8.png)

와 같이 `runtime_error` 예외를 발생시키며 프로그램이 비정상적으로 종료되었다고 뜨게 됩니다. 따라서, 언제나 예외를 던지는 코드가 있다면 적절하게 받아내는 것이 중요합니다. 하지만, 때로는 예외 객체 하나 하나 처리할 필요 없이 **그냥 나머지 전부다!** 라고 쓰고 싶을 때가 있습니다. 마치 `switch` 문의 `default` 이나 `if-else` 문에서 마지막 `else` 와 같이 말입니다.

재미있게도 `try .. catch` 문에서도 이를 잘 지원합니다.

```cpp-formatted
#include <iostream>
#include <stdexcept>

int func(int c) {
  if (c == 1) {
    throw 1;
  } else if (c == 2) {
    throw "hi";
  } else if (c == 3) {
    throw std::runtime_error("error");
  }
  return 0;
}

int main() {
  int c;
  std::cin >> c;

  try {
    func(c);
  } catch (int e) {
    std::cout << "Catch int : " << e << std::endl;
  } catch (...) {
    std::cout << "Default Catch!" << std::endl;
  }
}
```

성공적으로 컴파일 하였다면

![예외가 제대로 처리됨](/img/12.1.7.png)

와 같이 나옵니다.

마지막 `catch(...)` 에서 `try` 안에서 발생한 모든 예외들을 받게 됩니다. 당연히도, 어떠한 에외도 다 받을 수 있기 때문에 특정한 타입을 찝어서 객체에 대입 시킬 수 는 없겠지요.

```lec-warning
템플릿으로 정의되는 클래스의 경우 어떠한 방식으로 템플릿이 인스턴스화 되냐에 따라서 던지는 예외의 종류가 달라질 수 있습니다. 이 때문에 해당 객체의 `catch` 에서는 모든 예외 객체를 고려해야 합니다.
```

### 예외를 발생시키지 않는 함수 - noexcept

만약에 어떤 함수가 예외를 발생시키지 않는다면 `noexcept` 를 통해 명시할 수 있습니다.

```cpp
int foo() noexcept {}
```

foo 함수의 경우 예외를 발생시키지 않으므로 위와 같이 함수 정의 옆에 `noexcept` 를 넣음으로써 나타낼 수 있습니다. 참고로, 함수에 `noexcept` 키워드를 붙였다고 해서, 함수가 예외를 절대로 던지지 않는다는 것은 아닙니다. 실제로

```cpp
#include <iostream>

int foo() noexcept {}

int bar(int x) noexcept {
  throw 1;
}

int main() {
  foo();
}
```

이라고 해도 (경고는 뜨지만) 문제 없이 컴파일 합니다. 즉 컴파일러는 `noexcept` 키워드가 붙은 함수가 **이 친구는 예외를 발생시키지 않는구나** 라고 곧이곧대로 믿고, 그대로 컴파일 하게 됩니다. 

대신 `noexcept` 로 명시된 함수가 예외를 발생시키게 된다면 예외가 제대로 처리되지 않고 프로그램이 종료됩니다. 예를 들어서

```cpp
#include <iostream>

int foo() noexcept {}

int bar() noexcept { throw 1; }

int main() {
  foo();
  try {
    bar();
  } catch (int x) {
    std::cout << "Error : " << x << std::endl;
  }
}
```

성공적으로 컴파일 하였다면

```exec
terminate called after throwing an instance of 'int'
[1]    4331 abort (core dumped)  ./test
```

와 같이 `catch` 문에서 예외가 제대로 처리되지 않고 프로그램이 종료됨을 알 수 있습니다.

그렇다면 이 `noexcept` 키워드를 왜 붙이는 것일까요? 이는 단순히 프로그래머가 컴파일러에게 주는 힌트라고 생각하시면 됩니다. 컴파일러가 어떤 함수가 절대로 예외를 발생시키지 않는다는 사실을 안다면, 여러가지 추가적인 최적화를 수행할 수 있습니다.

```lec-warning
C++ 11 에서 부터 소멸자들은 기본적으로 `noexcept` 입니다. 절대로 소멸자에서 예외를 던지면 안됩니다.
```

자 그럼 이것으로 C++ 에서의 예외 처리에 관한 강좌를 마치도록 하겠습니다. C++ 스타일 예외 처리를 통해 좀 더 안정적인 프로그램을 만들 수 있습니다!

### 생각 해보기

#### 문제 1

C++ 표준 라이브러리에 자주 사용할만한 예외 객체들이 정의가 되어있습니다. [여기](http://www.cplusplus.com/reference/stdexcept/) 를 참고해서 읽어보세요!

##@ chewing-cpp-end