일지

Effective C++...27

niamdank 2022. 7. 19. 08:18

예외 안전성이 확보되는 그날 위해 싸우고 또 싸우자!

예외 안전성을 가진 함수의 동작

예외 안전성을 가진 함수는 다음과 같이 동작해야 한다.

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

 

다음의 예제의 new Image(imgSrc)에서 에러가 발생하면 이 동작이 모두 정상적으로 작동하지 않는다.


class PrettyMenu {

public:

    ...

    void changeBackground(std::istream& imgSrc); // 이미지 변경 함수

    ...

private:

    Mutext mutext;

    Image* bgImage; // 현재 배경

    int imageChanges; // 이미지 변경 횟수

}

 

void PrettyMutext::changeBackground(std::istream& imgSrc)

{

    lock(&mutex); // mutext가 unlock이 호출되지 않을 수 있다.

    delete bgImage;

    ++imageChanges; // 이미지가 변경되지 않았는데 변경 횟수만 증가할 수 있다.

    bgImage = new Image(imgSrc);

    unlock(&mutex);

}


 

이러한 문제는 [Effective C++] 자원관리 - 자원 관리 클래스의 복사 동작에 대해 진지하게 고찰하자 항목에 나오는 Lock을 사용하면 막을 수 있다.


void PrettyMutext::changeBackground(std::istream& imgSrc)

{

    Lock m1(&mutex);

    delete bgImage;

    ++imageChanges;

    bgImage = new Image(imgSrc); 

}


 

예외 안전성을 갖춘 함수가 제공해야 하는 보장

예외 안전성을 갖춘 함수는 다음의 세 가지 보장 중 하나를 제공해야 한다.

  • 기본적인 보장 함수 동작 중에 예외가 발생하면, 실행 중인 프로그램에 관련된 모든 것들을 유효한 상태로 유지한다.
  • 강력한 보장 함수 동작 중에 예외가 발생하면, 프로그램의 상태를 절대로 변경하지 않는다.
  • 예외 불가 보장 예외를 절대로 던지지 않는다.

 

기본적인 보장의 경우 프로그램의 상태가 어떻게 될지 예상이 불가능하다.

가령 changeBackground의 경우 예외 발생 시 bgImage가 예전 이미지가 될지 새 이미지가 될지 알 수 없다.

 

예외 불가 보장의 경우 C++가 아닌 C로 구현해야 할 가능성이 높아진다.

단적으로 메모리 할당을 실패할 경우에도 bad_alloc 예외가 던져진다.

 

따라서 세 가지 보장 중 강력한 보장이 가장 실용적이라고 할 수 있다.

 

강력한 보장 전략과 한계

강력한 보장을 제공하기 위한 도구로 스마트 포인터와 복사 후 맞바꾸기 전략을 쓸 수 있다.


struct MPImpl {

    std::tr1::shared_ptr<Image> bgImage;

    int imageChanges;

}

 

class PrettyMenu {

public:

    ...

    void changeBackground(std::istream& imgSrc); // 이미지 변경 함수

    ...

private:

    Mutext mutext;

    std::tr1::shared_ptr<MPImpl> pImpl;

}

 

void PrettyMutext::changeBackground(std::istream& imgSrc)

{

    using std::swap;

 

    Lock m1(&mutex);

    std::tr1::shared_ptr<MPImpl> pNew(new PMImpl(*pImpl)); // 기존 값의 복사본을 생성한다.

    pNew->bgImage.reset(new Image(imgSrc)); // 복사본의 값을 수정한다.

    ++pNew->imageChanges;

    swap(pImpl, pNew); // 복사본과 기존 값을 교환한다.

}


 

이런 구현을 통해 에러가 발생하더라도 기존 값이 유지된다는 보장을 할 수 있게 된다.

 

그러나 모든 함수가 강력한 보장을 할 수는 없다.

가령 데이터베이스가 변경되는 경우에는 강력한 보장이 불가능할 것이다.

 

※ 강력한 보장은 가능한 경우 제공하고 기본적인 보장은 제공할 수 있도록 해야 한다.