ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Effective C++...27
    일지 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); // 복사본과 기존 값을 교환한다.

    }


     

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

     

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

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

     

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

     

    댓글

Designed by Tistory.