Language/C++

[C++] 31.Unmanaged Programming: Exception

coco_daddy 2024. 1. 31. 00:48

1. 예외 처리 사용에 대한 주의

  • 남용해서는 안 된다.
  • 대부분의 예외는 불필요하다. (생성자의 경우에는 유용)

2. 예외를 사용해야 하는 상황

  • 나 이외의 문제로 발생하는 예외이기 때문에 control 할 수 없는 경우

3. 사람의 사고 방식은 선형적

  • 사람은 순서대로 읽는다.
  • 여러 문제를 한 번에 처리하려고 하면 필연적으로 실수가 생긴다.

4. 언어 자체적으로 주는 예외는 없다.

  • 프로그래머가 직접 만든 것.
  • 표준 라이브러리에서 예외를 던져주기는 한다. (std::exception 클래스를 상속받아 사용)

5. 예외상황이 아닌 예외 상황

  • 범위(range) 이탈
#include <exception>

int main()
{
	std::string catName = "Coco"

	try
	{
		char ch = myCatName.at(5)
	}
	catch (const std::out_of_range& e)
	{
		std::cerr << "out of range: " << e.what() << std::endl;
	}
	catch (const std::exception& e) // out_of_range가 받지 못한 예외를 여기서 모두 받겠다.
	{
		std::cerr << "exeption: " << e.what() << std::endl;
	}
}
  • 순서대로 catch함
  • 사실 if문으로 해결할 수 있음.
if (index < catName.size())
{
	char ch = catName.at(index)
}

6. 0으로 나누기

  • 컴파일러가 잡아주거나 OS가 잡아준다.
  • 표준 라이브러리에서는 정의되지 않은 결과로 남겨둠.
    • 컴파일러 설계에 따라 다르다.
  • 인터럽트 기반의 예외는 느리다. (컨텍스트 스위칭 때문)

7. NULL 개체 참조

  • C++에서는 정의되어 있지 않기 때문에 OS가 인터럽트 방식으로 시그널을 발생시킴.
  • 예외라고 생각하기 힘들다.

8. OS의 예외와 C++의 예외

9. 이론적으로 생성자의 경우에는 예외가 필요하다.

  • 생성자에서 예외를 다루는 방법
    • 자체 예외를 던지는 방법
Inventory::Inventory(int count)
{
	mSlots = new int[count];
}
  • mSlot가 NULL인 경우.
    • 생성자는 반환값이 없어 체크를 할 수 없다.
    • 실패 시 NULL을 넣어줄 수 없다.
#include <exception>

struct SlotNullException : public std::exception
{
	const char* what() const throw ()
	{
		return ("Slot is NULL");
	}
}
  • what()은 무슨 예외인지 알려주는 string을 반환한다.
Inventory::Inventory(int count)
{
	mSlots = new int[count];
	if (mSlots == NULL)
	{
		throw SlotNullException();
	}
}
  • throw된 예외는 아래와 같이 처리한다.

❇︎중요 참고사항

일반적으로 C++를 사용하는 업계는 예외처리 기능을 활성화하지 않는다.

많은 C++ 컴파일러들이 성능을 위해 이 기능을 사용하지 않도록 되어있다.

메모리 부족으로 인한 예외 발생

  • 프로그램 종료
    • 크래시와 다를 것이 없음
  • 생성자 재호출
    • 메모리가 부족해서 개체를 생성할 수 없었던 것이기 때문에 의미가 없음.
    • 모든 생성자마다 NULL이 나올 가능성을 생각해서 NULL 핸들링을 하는 것은 비효율적이다.

> 프로그램이 터지도록 두는 것이 나을 수 있음.

10. 예외 안정성

  • 예외가 나도 프로그램이 계속 돌아가야 한다.
  • 예외는 프로그램을 좀비로 만든다.
    • 살아는 있는데, 정상 작동이라고 볼 수 없다.

11. 안 좋은 예와 좋은 예 비교

class CoffeeShop {
    void SellWithPoint(Customer customer, int itemID, int points) throws EmptyItemException {
        deductPoint(customer, points);
        if (isEmpty(itemID)) {
            throw new EmptyItemException();
        }
    }
};

포인트는 감소가 되었는데, 커피는 주지 못했다.

class CoffeeShop {
    void SellWithPoint(Customer customer, int itemID, int points) throws EmptyItemException {
        if (isEmpty(itemID)) {
            throw new EmptyItemException();
        }
        deductPoint(customer, points);
    }
};

→ early exit 방식으로 구현하면 된다..!

→ 이론적으로는 훌륭하지만 100% 예외 안전성을 가지는 프로그램을 짜는 것이 쉬운 일이 아니다.

12. 예외 처리 사용에 대한 결론

  • 사람은 무엇을 읽어도 위에서부터 아래로 읽는다.
  • 100% 예외 안전성을 가지는 프로그램을 짜는 것이 어렵다.
  • 예외 처리는 필요한 경우에만 사용하는 것이 좋다.

포프 선생님의 팁!