일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
Tags
- Vector
- more effective c++
- 반복자
- 참조자
- resource management class
- 상속
- c++
- virtual function
- Smart Pointer
- 예외
- 암시적 변환
- 티스토리챌린지
- 스마트 포인터
- 비교 함수 객체
- effective stl
- 루아
- 영화
- UE4
- 오블완
- exception
- operator new
- reference
- Effective c++
- 언리얼
- 영화 리뷰
- 메타테이블
- lua
- implicit conversion
- 게임
- 다형성
Archives
- Today
- Total
스토리텔링 개발자
[Effective C++] 29. 예외 안전성 본문
728x90
항목 29 : 예외 안전성이 확보되는 그날을 위해 싸우고 또 싸우자!
예외 안정성을 고려하지 않은 코드
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);
}
예외 안전성을 확보하기 위한 두 가지 요구사항
- 자원이 새도록 만들지 않는다.
- 하지만 위의 코드는 자원이 샌다.
- "new Image(imgSrc)" 표현식에서 예외를 던지면?
- unlock 함수가 실행되지 않게 되어 뮤텍스가 계속 잡힌 상태로 남게 된다.
- 해결법
- 자원 관리 객체를 사용한다.(항목 14 참조)
-
{ Lock ml(&mutex); // 유효 범위 내에서만 동작하는 자원 관리 객체 사용 delete bgImage; ++imageChanges; bgImage = new Image(imgSrc); }
- 자료구조가 더럽혀지는 것을 허용하지 않는다.
- 자료구조가 의도한 바를 거스른다.
- "new Image(imgSrc)" 표현식에서 예외를 던지면?
- bgImage가 가리키는 객체는 이미 삭제된 후이다.(쓰레기 포인터가 된다.)
- bgImage가 invalid함에도 imageChanges변수가 증가한다.
예외 안전성을 갖춘 함수가 선택할 수 있는 세 가지 보장
- 기본적인 보장(basic guarantee)
- 함수 동작 중 예외가 발생하면, 실행 중인 프로그램에 관련된 모든 것들을 유효한 상태로 유지하겠다고 보장한다.
- 모든 객체 상태는 일관성을 유지한다.(즉, 모든 클래스 불변속성이 만족된 상태이다.)
- 하지만 프로그램 상태를 예측하는 건 불가할 수 있다.
- 예컨대, changeBackground 함수 동작 중 예외가 발생했을 경우.
- PrettyMenu 객체는 이전 배경그림을 그대로 계속 그릴 수도 있고, 아니면 초기값 기본 배경그림을 사용할 수도 있을 것이다.
- 즉, 유효성이 보장되긴 하나 결과 예측이 불가능하다.
- 강력한 보장(strong guarantee)
- 함수 동작 중 예외가 발생하면, 프로그램 상태를 절대로 변경하지 않겠다고 보장한다.
- 호출 성공 시 마무리까지 완벽히 성공하지만, 실패 시 호출이 없었던 것처럼 프로그램 상태가 되돌아간다.
- 예외 불가 보장(nothrow guarantee)
- 예외를 절대로 던지지 않겠다고 보장한다.
- 약속한 동작은 언제나 끝까지 완수한다.
- 기본 제공 타입(int, 포인터 등)에 대한 모든 연산은 예외를 던지지 않도록 되어있다.
- 어떤 예외도 던지지 않게 예외 지정이 된 함수는 예외 불가 보장을 제공한다고 생각하는 것은 잘못된 생각이다.
-
int doSomething() throw(); // 비어있는 예외 지정 // 모던 c++에서는 noexcept 로 대체되었다.
- 이 경우 doSomething은 절대 예외를 던지지 않겠다는 말이 아니디.
- 만약 예외가 발생하면 매우 심각한 에러이므로 unexpected 함수(지정되지 않은 예외 발생 시 실행되는 처리자)가 호출되어야 한다는 뜻이다.
- 함수가 어떤 특성을 갖느냐는 구현이 결정한다. 선언은 그저 선거 공약 같은 것이다.
-
- 예외 불가 보장이 좋겠지만, 현실적으로는 대부분 기본적인 보장 혹은 강력한 보장 중 한 가지를 선택하게 된다.
위 코드도 예외 시 자료구조를 더럽히지 않도록 해결해보자
-
class PrettyMenu { ... shared_ptr<Image> bgImage; ... }; void PrettyMenu::changeBackground(istream& imgSrc) { Lock ml(&mutex); bgImage.reset(new Image(imgSrc)); // 자원관리 포인터로 바꾸어 예외 처리를 전담시킨다. // 이로써 자원 할당 중 예외가 발생해도 bgImage는 이전 값이 유지된다.(강력한 보장) ++imageChanges; // bgImage가 실제 변경된 후로 옮겨 자료구조를 보장해준다. }
- 강력한 보장처럼 보이지만 아직까지는 기본적인 보장에 불과하다.
- 매개변수 imgSrc가 'new Image(imgSrc)'에서 예외가 발생할 시, 입력 스트림의 읽기 표시자가 이동한 채로 남아 있을 가능성이 충분히 있다.
- 다양한 방법으로 보완이 가능할 것이다.
- imgSrc를 값복사하여 전달한다.
- img의 path를 전달해서 Image 생성자에서 istream을 생성한다.
복사 후 맞바꾸기(copy-andswap) 전략
- '강력한 보장'을 쉽게 제공하는 전략이다.
- 객체를 수정하고 싶을 때, 그 객체의 사본을 하나 만들어 놓고 그 사본을 수정한다.
- pimpl 관용구를 사용하여 구현하는 것이 일반적이다.
-
struct PMImpl { shared_ptr bgImage; int imageChanges; }; class PrettyMenu { ... private: Mutex mutex; shared_ptr<PMImpl> pImpl; }; void PrettyMenu::changeBackground(istream& imgSrc) { using std::swap; Lock ml(&mutex); shared_ptr<PMImpl> pNew(new PMImpl(*pImpl)); // pImpl을 복사한다. // 복사 객체에 대해 작업한다. pNew->bgImage.reset(new Image(imgSrc)); ++pNew->imageChanges; swap(pImpl, pNew); // 스왑한다. }
- 전부 바꾸거나 혹은 안 바꾸거나(all-or-nothing) 방식으로 유지하기 수월하다.
- 하지만, 함수 전체를 '강력한 보장'이라 하긴 힘들다.
- 예를 들자면 아래의 경우 '강력한 보장'이 아니다.
-
void someFunc() { .... // 이 함수의 현재 상태를 사본으로 한다. f1(); f2(); // f1(), f2()의 예외 안전성이 강력하지 않으면 // someFunc() 역시 강력한 예외 안전성을 제공한다 볼 수 없다. .... // 변경된 상태를 바꾸어 넣는다. }
- 문제점
- f1, f2가 강력한 예외 안정성을 지원한다는 보장이 없다.
- f1, f2가 강력한 예외 안정성을 보장한다 쳐도
- f1이 성공한 후
- f2이 예외를 던짐
- 의 상황이라면 f1에 의해 이미 변화한 상태를 가지므로, someFunc는 강력한 보장이라 할 수 없다.
- 대다수의 함수에 있어 무리 없는 선택을 한다면 기본적인 보장이 우선이다.
- 어떤 함수가 제공하는 예외 안정성 보장의 강도는, 그 함수가 내부적으로 호출하는 함수들이 제공하는 가장 약한 보장을 넘지 않는다.
- 예외에 안전하거나, 예외에 뚫려 있거나 둘 중 하나이다. 일부만 예외 안전성을 갖춘 시스템 같은 것은 없다.
728x90
'개발 > Effective C++' 카테고리의 다른 글
[Effective C++] 31. 컴파일 의존성 줄이기 (0) | 2024.06.27 |
---|---|
[Effective C++] 30. 인라인 함수 (0) | 2024.06.26 |
[Effective C++] 28. 클래스 내부 private 객체에 대한 핸들 리턴 문제 (0) | 2024.06.24 |
[Effective C++] 27. 캐스팅 (0) | 2024.06.21 |
[Effective C++] 26. 필요한 시점 직전에 변수를 정의할 것 (0) | 2024.06.20 |
Comments