C++ 아이콘 제작자: Darius Dan - Flaticon
std::vector<int> v(3); // 크기가 3 인 벡터 만듦
std::cout << v.at(4); // ?? ==> 오류발생
std::vector<int> v(1000000000); ==> 평범한 시스템의 경우 이렇게 큰 메모리를 할당할 수 없음
이렇게 정상적인 상황에서 벗어난 모든 예외적인 상황들을 예외(exception)이다.
c언어에선 문제가 생길만한 곳에 if문으로 return하는 것으로 예외를 처리했지만 c++은 명시적으로 예외가 발생했음을 표시할 수 있다. ==> throw
간단하게 백터 클래스를 만든다고 생각하자
at함수를 만드는데 접근하려는 범위가 해당 벡터 크기 범위 이내라면 data[index] 그냥 리턴하면 되지만
범위 밖이라면??
문제는 at함수는 리턴값이 const T& 이라서 따로 리턴하는 것으로 처리 불가능throw로 명시적으로 알린다.
if (index >= size) throw std::out_of_range("vector 의 index 가 범위를 초과하였습니다.");
C++ 에는 예외를 던지고 싶다면, throw로 예외로 전달하고 싶은 객체를 써주면 됩니다.
예외로 아무 객체나 던져도 상관 없지만 C++ 표준 라이브러리에는 여러가지 종류의 예외들이 정의되어 있다.
이렇게 예외를 throw 하게 되면, throw 한 위치에서 즉시 함수가 종료되고
예외 처리하는 부분까지 점프한다. 그러면 throw 밑의 로직은 실행되지 않음.
try {
data = vec.at(index);
} catch (std::out_of_range& e) {
std::cout << "예외 발생 ! " << e.what() << std::endl;
}
우선 try 부분은 예외가 발생할 만한 지역을 지정한다.
catch는 예외가 발생되면 실행되는 코드 ==> 예외를 처리한다.catch 블록은 항상 try 블록의 뒤에 이어서 등장해야 하며, try 블록에서 발생한 예외는 catch 블록에서 처리된다.
만약 예외가 일어나지 않는다면 그냥 try ... catch 부분이 없는 것처럼 실행된다.
- 예외가 발생한다면 위에서 말한 stack에 생성된 모든 객체의 소멸자가 호출되고 (이와 같이 catch 로 점프 하면서 스택 상에서 정의된 객체들을 소멸시키는 과정을 스택 풀기(stackunwinding) 이라고 한다.)
가장 가까운 catch문으로 점프한다.위의 경우 throw 다음 아래 로직이 실행된다.
catch (std::out_of_range& e) {std::cout << "예외 발생 ! " << e.what() << std::endl;
여기서 catch 문은 throw에서 던진 객체에 맞는 객체를 받는다.
위에서throw std::out_of_range("vector 의 index 가 범위를 초과하였습니다.");
out_of_range 를 throw 하였는데, 위 catch 문이 out_of_range를 받으므로, 잘 처리한다.
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!";
}
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;
}
상속을 받는 클래스에서 처리하는 방식이 따로 있다.
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();
} // 1입력시 -> Parent Catch! Parent! 출력
} // 2입력시 -> Parent Catch! Child! 출력
Parent 클래스 객체를 throw할 때는 잘 작동하는데 Child 객체는 이상하다.
- 이와 같은 일이 발생한 이유는 catch 문의 경우 가장 먼저 대입될 수 있는 객체를 받기 때문이다.
Parent& p = Child(); 는 가능하기 때문에 Parent catch 가 먼저 받아버리는 것
- 위와 같은 문제를 방지하기 위해서는 언제나 Parent catch 를 Child catch 보다 뒤에 써야한다.
- 왜냐하면 이를 통해서 Child 객체가 Parent catch 에 들어가는 것을 막을 수 있고
Child &c = Parent(); // 오류- 위는 성립되지 않기 때문에 Child catch 에 Parent 객체가 들어가지도 않는다.
일반적으로 예외 객체는 std::exception 을 상속 받는 것이 좋다. 왜냐면 유용한 함수들이 있어서...
switch문의 default랑 if-else문에서 마지막 else와 같은 기능이 존재한다.
이 기능을 사용하자. 대신 어떤 예외도 다 받기에 특정한 타입에 대해 적용X
- 컴파일러는 noexcept 키워드가 붙은 함수가 예외를 발생시키지 않는다는 것을 믿고
그대로 컴파일 한다- 대신 noexcept 로 명시된 함수에서 예외가 발생되면 예외가 제대로 처리되지 않고 프로그램이 종료돤다.
개인 공부 기록용 블로그입니다.
틀린 부분 있으다면 지적해주시면 감사하겠습니다!!