기존에는 아래와 같이 if else문으로 오류 처리를 진행
char *c = (char *)malloc(1000000000);
if (c== null){
printf("메모리 할당 오류!");
return;
}
함수가 쌓이고 딥하게 들어간 함수에서(함수1() -> 함수2() -> 함수3()인데 함수3에서) 예외 처리하는 경우, 함수가 복잡하게 쌓이고, 중간에 다른 요인으로 예외 처리를 하고 싶을 때 그걸 할 수 없다.
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_;
};
throw로 <예외로 전달하고 싶은 객체>를 써주면 된다.
throw한 위치에서 즉시 함수가 종료되고, 예외 처리하는 부분까지 점프함.
throw밑 문장은 모두 실행되지 앟음. 함수를 빠져나가며, stack에 생성된 객체들을 빠짐없이 소멸시켜 준다(소멸자만 제대로 작성하면).
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;
}
이때, out_of_range 클래스는 문자열 필드 하나 달랑 있고 이 역시 what() 함수로 그 값을 들여다 볼 수 있음.
위 경우 vector의 index가 범위를 초과했습니다가 나오게 됨
스택 풀기: catch로 점프하면서 스택 상에서 정의된 객체들을 소멸시키는 과정
throw를 하게 되면 가장 가까운 catch로 점프한다
#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();
}
}
생성자에서 예외가 발생할 때 소멸자가 호출되지 않음.
catch문은 아래와 같이 여러 종류의 자료형으로 받을 수 있음.
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;
}
예외는 순서대로 들어가므로
func(c)에서 예외로 Child가 발생해도 Patent& p로 예외 처리가 진행된다(첫번쨰 catch문).
#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();
}
}
어떤 예외를 throw했는데, 이를 받는 catch가 없으면
catch(...)
쓰면 된다.
try {
func(c);
} catch (int e) {
std::cout << "Catch int : " << e << std::endl;
} catch (...) {
// 여기서 try 안에서 발생한 모든 예외들을 받음. 어떤 예외도 다 받을 수 있어서 특정 타입을 찝어 객체에 대입시킬 수는 없음.
std::cout << "Default Catch!" << std::endl;
}
어떤 함수가 예외를 안 발생시키면 noexcept를 통해 명시할 수 있음.
int foo() noexcept{}
foo 함수의 경우 예외를 발생시키지 않아 noexcept를 넣어서 나타낼 수 있음.
❗함수에 noexcept 키워드를 붙였다고 해서 함수가 예외를 절대로 던지지 않는 건 아님.
noexcept 붙이는 이유: 컴파일러가 추가적인 최적화를 수행할 수 있음