[Modern C++] 11. 예외처리

윤정민·2023년 7월 11일
0

C++

목록 보기
30/46

1. 예외

문법 상 문제가 없지만, 실행시 오류가 발생했을 때 즉, 정상적인 상황에서 벗어난 모든 예외적인 상황들을 예외(exception)이라고 부름

2. 예외처리

2.1. 일반적인 방법

  • 조건문을 사용해 예외 상황을 인지 및 처리
    • 함수가 깊어질 수록 귀찮아짐
char *c = (char *)malloc(1000000000);
if (c == NULL) {
  printf("메모리 할당 오류!");
  return;
}

2.2 C++의 예외 처리 방식

2.2.1. 예외 발생시키기-throw

  • 예외가 발생했다는 사실을 throw 문을 통해 명시적으로 나타낼 수 있음
  • 예시: Vector클래스에서 at함수를 작성한다 생각하자
    • throw를 사용해 예외로 전달하고 싶은 객체를 써주면 됨
// 생략 ...
const T& at(size_t index) const {
  if (index >= size) {
    // 예외를 발생시킨다!
    throw std::out_of_range("vector 의 index 가 범위를 초과하였습니다.");
  }
  return data[index];
}
// 생략 ...
};
  • throw시 해당 위치에서 즉시 함수가 종료되고, 예외 처리하는 부분까지 점프하게 됨
    • 이때, stack에 생성되었던 객체들을 빠짐없이 소멸시켜줌(자원을 제대로 소멸시켜주니 너무 편함)

2.2.2. 예외 처리하기-try와 catch

  • try: 내부에 예외가 발생할만한 코드를 작성
    • 예외가 발생했다면: stack에 생성된 모든 객체들의 소멸자들이 호출되고, 가장 가까운 catch문으로 점프
    • 예외가 발생하지 않았다면: try+catch가 없는것처럼 실행됨
  • catch: throw된 예외를 받아 예외시 행동을 실행
  • 예시 코드
#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;
}

2.3. 스택 자원의 소멸?

  • 스택 풀기: 가장 가까운catch로 점프하면서 스택상에서 정의된 객체들을 소멸시키는 과정

3. 여러 종류의 예외 받기

  • catch는 여러 종류의 예외를 받을 수 있어 한 개의 try안에 받고자 하는 모든 종류의 예외를 catch문으로 주렁주렁 달면 됨
#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;
  }
}

4. 기반 클래스와 파생 클래스의 예외처리

  • 기반 클래스의 예외를 발생시키면 기반 클래스의 catch로 점프됨
  • 파생 클래스의 예외를 발생시키면 기반 클래스의 catch로 점프될 수 있음(더 가까우면)
    • 파생클래스 is a 기반클래스이기 때문에 기반 클래스의 catch에서 작동될 수 있음
  • 따라서 파생 클래스의 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();
  }
}

5. 모든 예외 받기

  • switch문의 defaultif-elseelse처럼 ...를 사용하면 검색되지 않은 모든 예외를 처리 가능
  • 템플릿으로 정의되는 클래스의 경우 어떠한 방식으로 템플릿이 인스턴스화 되냐에 따라서 던지는 예외의 종류가 달라질 수 있기 때문에 catch에서는 모든 예외 객체를 고려해야 함
  try {
    func(c);
  } catch (int e) {
    std::cout << "Catch int : " << e << std::endl;
  } catch (...) {
    std::cout << "Default Catch!" << std::endl;
  }
}

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

  • 예외를 발생시키지 않는다면 noexcept를 통해 명시할 수 있음
  • 컴파일러가 어떤 함수가 절대로 예외를 발생시키지 않는다는 사실을 안다면, 여러가지 추가적인 최적화를 수행할 수 있음
profile
그냥 하자

0개의 댓글