C++ 에서의 예외 처리
throw, try, catch
char *c = (char *)malloc(1000000000);
if (c == NULL)
{
printf(" malloc error ");
return ;
}
malloc 의 경우 메모리 할당 실패시에 NULL을 리턴 하므로 ,
위와같이 c가 null인지 확인하는걸로 예외처리를 한다.
하지만 위와 같은 방법은 함수가 깊어질수록 귀찮아진다
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 입장에서 fun3에서 문제가 발생했을때 이를 캐치 하기 위해서는
각각의 함수들에서 처리 결과를 모두 리턴해야합니다.
즉, 위 코드가 만약에 func2도 어떤 다른 작업을 해서 예외를 발생시킬수 있다면 어떻게 해야 할까요?
const T& at(size_t index) const {
if (index >= size_) {
throw out_of_range("vector 의 index 가 범위를 초과하였습니다.");
}
return data_[index];
}
throw std::out_of_range("vector 의 index가 범위를 초과했습니다.");
**
#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;
}
try {
data = vec.at(index);
}
try안에서 무언가 예외가 발생할 만한 코드가 실행된다
data는 vec의 index 번째 값이 들어가고
catch 문은 무시된다.
반면 예외가 발생할경우
그 즉시 stack에 생성된 모든 객체들의 소멸자들이 호출되고
가장 가까운 catch문으로 점프한다.
위의 경우
if (index >= size_) {
throw std::out_of_range("vector 의 index 가 범위를 초과하였습니다.");
}
catch (std::out_of_range& e) {
std::cout << "예외 발생 ! " << e.what() << std::endl;
}
이 catch 부분이다
여기서 catch 문은 throw 된 예외를 받는 부분,
어떤 예외를 받냐면
catch 문 안에 정의된 예외의 꼴에 맞는 객체를 받게 된다.
out_of_range 클래스는 아주 간단한데, 그냥 내부에 발생엔 예외에 관한 내용을 저장하는 문자열 필드가 달랑 하나 있고 이 역시 what() 함수로 그 값을 들여다 볼 수 있습니다.
위 경우 우리가 전달한 문장인 'vector의 index가 범위를 초과하였습니다' 가 나오게 됩니다.
#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();
}
}
>>
리소스 해제 : 3
리소스 해제 : 2
리소스 해제 : 1
Exception : Exception from 3!
func3 은 func2 가 호출하고 func2 는 func1 이 호출 하고 func1은 main()에서 호출한다.
예외가 발생하게 되면 가장 가까운 catch 에서 예외를 받는다.
그런데 func1, 2 모두 catch가 없으므로
가장 가까운 catch문은 main함수에 있는 catch 구문이 된다.
실제로도 예외가 main함수 까지 잘 전달되어서 출력 되었다.
또 한가지 중요한 점은 예외가 전파되면서 각 함수들에 정의 되어있던 객체들이 잘 소멸 되었다는 점이다.
만약 func3에서 throw하지 않는다면
#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();
}
}
>>
리소스 해제 : 3
실행!
리소스 해제 : 2
실행!
리소스 해제 : 1
정상적일 경우 실행! 이 출력되고 리소스 해제 되는데
그 전 예외 발생 코드에서는 예외 발생시 바로 catch문으로 점프 하면서 각 함수들에 있던 객체들만 해제 하기 때문에
리소스는 해제는 정상적으로 되고 출력문도 잘 나오지만
그사이에 있는 "실행 안됩!" 부분은 건너 뛴다.
이와 같이 catch 로 점프 하면서 스택 상에서 정의된 객체들을 소멸시키는 과정을 스택 풀기(stack unwinding) 이라고 부릅니다.
-예외를 생성자에서 던질 때 주의해야 할 점이 하나 있습니다.
- 바로 생성자에서 예외가 발생 시에 소멸자가 호출되지 않는다 라는 점입니다.
- 따라서, 만일 예외를 던지기 이전에 획득한 자원이 있다면 catch 에서 잘 해제시켜 줘야만 합니다.
pass~