[Effective C++] 항목29 : 예외 안정성이 확보되는 그날 위해 싸우고 또 싸우자!

Jangmanbo·2024년 4월 16일
0

Effective C++

목록 보기
29/33

PrettyMenu 클래스

  • 배경 그림이 있는 GUI 메뉴를 구현하기 위한 클래스
  • 스레딩 환경에서 동작할 수 있도록 병행성 제어를 위한 mutex를 갖고 있다.
class PrettyMenu
{
public:
	...
    void changeBackground(istream& imgSrc);	// 배경 그림을 바꾸는 멤버 함수
    ...
    
private:
	Mutex mutex;
    
    Image* bgImage;		// 현재 배경 그림
    int imageChanges;	// 배경 그림이 바뀐 횟수
};

void PrettyMenu::changeBackground(istream& imgSrc)
{
	Lock(&mutex);	// 뮤텍스 획득
    
    delete bgImage;
    ++imageChanges;
    bgImage = new Image(imgSrc);
    
    unlock(&mutex);	// 뮤텍스 해제
}

여기서 changeBackground함수는 예외 안정성을 확보하지 못했다.

예외 안정성을 확보하는 두 가지 조건

  • 자원이 새도록 만들지 않는다.
  • 자료구조가 더렵혀지는 것을 허용하지 않는다.

changeBackground가 예외 안정성을 확보하지 못한 이유

  • 자원 누출 문제: new Image(imgSrc)에서 예외를 던지면 unlock 함수가 호출되지 않아 뮤텍스가 계속 잡힌 상태로 남게 된다.
  • 자료구조 오염 문제: new Image(imgSrc)에서 예외를 던졌을 때 bgImage는 이미 삭제된 후며, 새 그림으로 변경되지 않았음에도 imageChanges는 증가한다.

자원 누출 문제 해결 방법

void PrettyMenu::changeBackground(istream& imgSrc)
{
	Lock ml(&mutex);	// 항목 14 참고. 뮤텍스가 필요 없어질 시점에 바로 해제해주는 객체
    
    delete bgImage;
    ++imageChanges;
    bgImagd = new Image(imgSrc);
}

자원 누출 문제는 객체를 통해 자원 관리를 전담케 하고(항목 13), 자원이 필요없어질 시점에 해제(항목 14)하면 해결할 수 있다.


예외 안정성의 종류

예외 안정성을 갖춘 함수는 아래 세가지 보장 중 하나를 반드시 제공한다.
따라서 어떤 예외 안정성을 보장할 것인지 선택해야 한다.

기본적인 보장

함수 동작 중에 예외 발생 시, 모든 것들을 유효한 상태로 유지한다는 보장이다. (=클래스 불변속성을 만족)
프로그램의 상태가 정확히 어떠한지 예측이 안된다는 단점이 있다.

강력한 보장

함수 동작 중에 예외 발생 시, 프로그램 상태를 절대로 변경하지 않겠다는 보장이다. (=원자적인 동작)
예측할 수 있는 프로그램의 상태가 단 두가지이다. (성공적으로 실행을 마친 상태 or 함수가 호출될 때의 상태)

예외불가 보장

예외를 절대로 던지지 않겠다는 보장으로, 약속한 동작은 언제나 끝까지 완수한다.

int doSomething() throw();

위 함수는 예외불가 보장을 제공하지 않는다.
여기서 throw()doSomething에서 지정되지 않은 예외가 발생했을 경우, terminate 함수가 호출된다는 의미이다.

Exception specifications (throw, noexcept) (C++)
(본 책에서는 예외가 발생하면 unexpected 함수가 호출된다고 하는데, 이는 C++14까지만 해당된다.
C++17 표준 및 std:++17 이상에서는 std::terminate가 호출된다.)



강력한 보장 제공하기

changeBackground 함수에 강력한 보장을 제공해보자.

  1. bgImage 멤버 타입을 자원관리 전담용 포인터로 변경
    • Image* -> shared_ptr<Image>
  2. changeBackground 함수 내 코드 재배치
    • 배경 그림이 진짜로 바뀐 후에 imageChanges 증가

class PrettyMenu
{
	...
    shared_ptr<Image> bgImage;
    ...
};

void PrettyMenu::changeBackground(istream& imgSrc)
{
	Lock ml(&mutex);
    
    bgImage.reset(new Image(imgSrc));
    
    ++imageChanges;
}

이전 코드와 비교했을 때 장점

  1. 이전 배경그림을 직접 delete하지 않아도 됨
  2. 새로운 배경그림이 제대로 만들어졌을 때만 이전 배경 그림을 삭제
    • delete 연산자가 reset함수 내에 있음
    • new Image(imgSrc)가 제대로 생성되어야 reset함수가 호출됨
  3. 자원관리 객체 shared_ptr를 사용하면서 함수의 길이가 줄어듦

// 복사 후 맞바꾸기 방법 작성 예정

예외 안정성 확보하는 방법
1. 자원 관리용 객체 사용
2. 새로 만드는 함수에 어떤 예외 안정성을 보장할 지 결정하고 문서로 남김


정리

  • 예외 안정성: 기본적인 보장, 강력한 보장, 예외 금지 보장
  • 예외 안정성을 갖춘 함수는 예외가 발생해도 자원을 누출시키지 않고 자료구조를 더럽힌 채로 두지 않는다.
  • 강력한 예외 안정성 보장은 '복사 후 맞바꾸기'방법으로 보장 구현 가능
    • 그러나 모든 함수에 대해 실용적이지는 않다.
  • 어떤 함수가 제공하는 예외 안정성 보장의 강도는 그 함수의 내부에서 호출하는 함수가 제공하는 가장 약한 보장이다.

0개의 댓글