readme.md
기록소
readme.md
전체 방문자
오늘
어제
  • 분류 전체보기
    • 네트워크
      • HTTP
      • 윈도우 소켓 프로그래밍
    • Windows API
    • 그래픽스
      • DirectX11
    • 일반
      • Linux
      • 데이터베이스
      • 팁
      • 책 후기
    • 쿠버네티스
    • 프로그래밍 언어
      • C#
      • Java
      • Go
      • C++
      • Lua
    • 책
      • 이펙티브 C++
      • 제프리 리처의 WINDOWS VIA C, C++
    • 기타

블로그 메뉴

  • 홈
  • 태그

공지사항

인기 글

태그

  • 생성자
  • Delete
  • new
  • 소멸자
  • id3d11shaderresourceview
  • DirectX
  • 인터페이스
  • 윈도우 소켓
  • 상속
  • 초기화
  • imagestride
  • 자바8
  • Graphics
  • 자원관리
  • 대입연산자
  • windowsAPI
  • emplace
  • const
  • phong
  • 템플릿
  • wm_keyup
  • 가상함수
  • C++
  • 버텍스 버퍼
  • CPP
  • 소켓 프로그래밍
  • consteval
  • 캐스팅
  • directx11
  • 설계

최근 댓글

최근 글

티스토리

hELLO · Designed By 정상우.
readme.md

기록소

책/이펙티브 C++

이펙티브 C++(201p ~ 210p)

2024. 1. 25. 22:17

이펙티브 C++
https://www.yes24.com/Product/Goods/17525589
페이지 201p ~ 210p

이펙티브 C++(201p ~ 210p)

요약

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

  • 다음 클래스는 스레딩 환경에서 동작할 수 있게 설계된 GUI 메뉴 클래스이다.
class PrettyMenu {
public:
    // 배경을 변경하는 함수
    void changeBackground(std::istream& imgSrc);

private:
    Mutex mutex;

    // 현재 배경
    Image* bgImage;

    // 변경된 횟수
    int imageChanges
};

void PrettyMenu::changeBackground(std::istream& imgSrc)
{
    lock(&mutex);

    delete bgImage;
    ++imageChanges;
    bgImage = new Image(imgSrc);

    unlock(mutex);
}
  • 예외 안전성 측면에서는 나쁜 함수이다.
  • 예외 안전성을 확보하기 위해 두 가지의 요구사항을 충족시켜야 한다.
    • 자원이 새도록 만들지 않는다. 위의 코드는 자원이 샌다. new 연산자를 사용하는 도중에 예외가 발생하면 unlock 함수가 호출되지 않아 mutex가 락된 상태로 남는다.
    • 자료구조가 더렵혀지는 것을 허용하지 않는다. 위의 코드는 new 연산자를 사용하는 도중에 예외가 발생하면 bgImage가 이미 삭제된 상태로 종료된다.
  • 자원 관리를 위한 객체를 이용해보자. 이를 통해 자원 누출 문제는 해결할 수 있다.
void PrettyMenu::changeBackground(std::istream& imgSrc)
{
    // 항목 14 참고
    Lock m1(&mutex);

    delete bgImage;
    ++imageChanges;

    bgImage = new Image(imgSrc);
}
  • 자료구조 오염을 해결하기 위한 용어 공부가 필요하다.
    • 기본적인 보장: 함수 동작 중에 예외가 발생하면, 실행 중인 프로그램에 관련된 모든 것들을 유효한 상태로 유지하겠다는 보장이다. 모든 클래스 불변속성이 만족된 상태이다. 하지만 프로그램의 상태를 정확히 예측할 수 없을 수 있다.
    • 강력한 보장: 함수 동작 중에 예외가 발생하면, 프로그램의 상태를 절대로 변경하지 않겠다는 보장이다. 이런 함수를 호출하는 것은 원자적인 동작이라 할 수 있다.
    • 예외불가 보장: 예외를 절대로 던지지 않겠다는 보장이다. 약속한 동작은 언제나 끝까지 완수하는 함수라는 뜻이다.
  • 예외 안전성을 갖춘 함수는 세 가지 보장 중 하나를 반드시 제공해야 한다.
    • 실용성의 측면에서는 강력한 보장이 괜찮다.
    • 예외 안전성 측면에서는 예외불가 보장이 괜찮다.
      • 그러나 실질적으로 예외를 던지는 함수를 호출하지 않고 C++의 C 부분으로부터 벗어나오긴 힘들다.
    • 현실적으로는 대부분의 함수에 있어서 기본적인 보장과 강력한 보장 중 하나를 고른다.
// 강력한 보장
class PrettyMenu {
public:
    // 배경을 변경하는 함수
    void changeBackground(std::istream& imgSrc);

private:
    // 포인터 대신 자원관리 객체 사용
    std::tr1::shared_ptr<Image> bgImageData;
};

// 문장 재배치
// Image 객체를 프로그래머가 직접 삭제하지 않음
// 새로운 배경그림이 제대로 만들어졌을 때만 이전 배경그림의 삭제 작업이 진행됨
void PrettyMenu::changeBackground(std::istream& imgSrc)
{
    Lock m1(&mutex);

    // reset 함수가 호출되려면 new Image가 정상적으로 생성되어야 함
    // delete 연산자는 reset 함수 안으로 들어감
    // 따라서 reset이 불리지 않으면 delete도 사용될 일이 없음
    bgImage.reset(new Image(imgSrc));

    ++imageChanges;
}
  • 위의 코드에서 옥의 티는 매개변수인 imgSrc에 대한 처리를 할 때이다.
    • Image 클래스의 생성자가 실행되다가 예외를 일으킬 때, 그 시점에 입력 스트림의 읽기 표시자가 이동한 채로 남아 있을 가능성이 있다.
    • 따라서 엄밀히 말하면 위의 함수는 기본적인 보장이라고 할 수 있지만, 일단은 넘어간다.

예외 안전성 보장을 위한 설계 전략

  • 복사 후 맞바꾸기라는 이름으로 알려져 있다.
  • 어떤 객체를 수정하고 싶으면 그 객체의 사본을 하나 만들고, 사본을 수정하는 것이다.
    • 이러면 사본 객체 수정 중에 예외가 발생해도 원본 객체는 바뀌지 않는다.
  • 수정 동작이 모두 완료되면 수정된 객체를 원본 객체와 맞바꾼다.
    • 이 작업을 예외를 던지지 않는 연산에서 처리한다.
struct PMImpl {
    std::tr1::shared_ptr<Image> bgImage;
    int imageChange;
}

class PrettyMenu {
    Mutex mutex;
    std::tr1::shared_ptr<PMImpl> pImpl;

public:
    void changeBackground(std::istream& imgSrc) {
                // 항목 25 참고
        using std::swap;
        Lock ml(&mutex);

        // 객체 데이터 부분 복사
        std::tr1::shared_ptr<PMImpl> pNew(new PMImpl(*pImpl));

        // 사본 수정
        pNew->bgImage.reset(new Image(imgSrc));

        // 맞바꾸기
        swap(pImpl, pNew);
    }   // release mutex
}
  • 복사 후 맞바꾸기 전략은 객체의 상태를 전부 바꾸거나 혹은 안 바꾸거나 방식으로 유지하려는 경우에 적합하다.
  • 그러나 함수 전체가 강력한 예외 안전성을 갖도록 보장하지는 않는다.
void changeBackground(std::istream& imgSrc) {
    using std::swap;
    Lock ml(&mutex);

    // 객체 데이터 부분 복사
    std::tr1::shared_ptr<PMImpl> pNew(new PMImpl(*pImpl));

        // 사본 수정 단계
    // 사본 수정
    pNew->bgImage.reset(new Image(imgSrc));

        // ...
        // 여기에서 호출되는 함수가 예외 안전성이 없을 수 있음

    // 사본 수정 단계 종료

    // 맞바꾸기
    swap(pImpl, pNew);
} 
  • 복사 후 맞바꾸기는 중간 단계에서 호출되는 함수가 예외 안전성이 강력하지 못하면, 전체 함수가 강력한 예외 안전성을 보장하기 힘들다.
    • 강력한 보장을 제공하게 만들려면 중간 함수를 호출하기 전에 프로그램 전체의 상태를 결정하고, 중간 함수에서 발생하는 모든 예외를 잡아낸 후에 원래의 상태로 돌아가는 코드를 작성해야 한다.
    • 그러나 중간 함수가 여러개 존재하면 사이드 이펙트를 무시할 수 없게 된다.
    • 또한, 복사 후 맞바꾸기 전략은 시간과 공간적인 측면에서도 무시할 수 없는 비용이 든다.
  • 강력한 예외 안전성 보장이 힘들면 기본적인 보장으로 눈을 돌려도 된다.

메모

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

  • 예외 안전성을 갖춘 함수는 실행 중 예외가 발생되더라도 자원을 누출시키지 않으며 자료구조를 더럽힌 채로 내버려 두지 않는다. 이런 함수들이 제공할 수 있는 예외 안전성 보장은 기본적인 보장, 강력한 보장, 예외 금지 보장이 있다.
  • 강력한 예외 안전성 보장은 복사 후 맞바꾸기 전략을 써서 구현할 수 있지만, 모든 함수에 대해 강력한 보장이 실용적인 것은 아니다.
  • 어떤 함수가 제공하는 예외 안전성 보장의 강도는, 그 함수가 내부적으로 호출하는 함수들이 제공하는 가장 약한 보장을 넘지 않는다.

'책 > 이펙티브 C++' 카테고리의 다른 글

이펙티브 C++(230p ~ 239p)  (0) 2024.01.29
이펙티브 C++(210p ~ 220p)  (2) 2024.01.26
이펙티브 C++(191p ~ 200p)  (0) 2024.01.25
이펙티브 C++(183p ~ 190p)  (1) 2024.01.23
이펙티브 C++(169p ~ 182p)  (2) 2024.01.22
    '책/이펙티브 C++' 카테고리의 다른 글
    • 이펙티브 C++(230p ~ 239p)
    • 이펙티브 C++(210p ~ 220p)
    • 이펙티브 C++(191p ~ 200p)
    • 이펙티브 C++(183p ~ 190p)
    readme.md
    readme.md

    티스토리툴바