C++에서의 예외 처리

·2022년 6월 20일
0

cpp_study

목록 보기
18/25

예외 처리

기존의 예외 처리 방식(C)

기존에는 아래와 같이 if else문으로 오류 처리를 진행

char *c = (char *)malloc(1000000000);
if (c== null){
  printf("메모리 할당 오류!");
  return;
}

문제점

함수가 쌓이고 딥하게 들어간 함수에서(함수1() -> 함수2() -> 함수3()인데 함수3에서) 예외 처리하는 경우, 함수가 복잡하게 쌓이고, 중간에 다른 요인으로 예외 처리를 하고 싶을 때 그걸 할 수 없다.

throw로 예외 발생시키기

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에 생성된 객체들을 빠짐없이 소멸시켜 준다(소멸자만 제대로 작성하면).

예외 처리하기 - try, catch

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가 범위를 초과했습니다가 나오게 됨

스택 풀기(stack unwinding)

스택 풀기: 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

어떤 함수가 예외를 안 발생시키면 noexcept를 통해 명시할 수 있음.

int foo() noexcept{}

foo 함수의 경우 예외를 발생시키지 않아 noexcept를 넣어서 나타낼 수 있음.
❗함수에 noexcept 키워드를 붙였다고 해서 함수가 예외를 절대로 던지지 않는 건 아님.

noexcept 붙이는 이유: 컴파일러가 추가적인 최적화를 수행할 수 있음

profile
이것저것 개발하는 것 좋아하지만 서버 개발이 제일 좋더라구요..

0개의 댓글