-
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); // 복사본과 기존 값을 교환한다.
}
이런 구현을 통해 에러가 발생하더라도 기존 값이 유지된다는 보장을 할 수 있게 된다.
그러나 모든 함수가 강력한 보장을 할 수는 없다.
가령 데이터베이스가 변경되는 경우에는 강력한 보장이 불가능할 것이다.
※ 강력한 보장은 가능한 경우 제공하고 기본적인 보장은 제공할 수 있도록 해야 한다.