[ Effective C++ ] 정리 모음집
" C++ 프로그래머의 필독서, 스콧 마이어스의 Effective C++ 를 읽고 내용 요약 / 정리 "
" 예외 안전성을 확보하는 작업은 아기를 갖는 일만큼 힘들다! "
- 예외 안전성을 갖춘 함수는 실행 중 예외가 발생되더라도 자원을 누출시키지 않으며 자료구조를 더럽힌 채로 내버려 두지 않는다.
- 강력한 예외 안전성 보장은 '복사 후 맞바꾸기' 방법을 써서 구현할 수 있지만, 모든 함수에 대해 강력한 보장이 실용적인 것은 아니다.
- 어떤 함수가 제공하는 예외 안전성 보장의 강도는, 그 함수가 내부적으로 호출하는 함수들이 제공하는 가장 약한 보장을 넘지 않는다.
[ 예외 안전성을 확보하려면 ]
- 자원이 새도록 만들지 않는다.
- 자료구조가 더렵혀지는 것을 허용하지 않는다.
[ 예시 코드 ]
class PrettyMenu
{
public:
...
void changeBackground(istream& imgSrc);
...
private:
Mutex mutex;
Image* bgImage;
int imageChanges;
};
void PrettyMenu::changeBackground(istream& imgSrc)
{
Lock(&mutex);
delete bgImage;
++imageChanges;
bgImagd = new Image(imgSrc);
unlock(&mutex);
}
위 코드는 자원 누출을 막지 못한다.
- new Image(imgSrc) 표현식에서 예외가 발생하면 unlock 함수가 실행되지 않아 뮤텍스가 계속 잡혀있는 상태
위 코드는 자료구조가 더렵혀지는걸 허용 함.
- new Image(imgSrc) 표현식에서 예외가 발생하면 bgImage가 가리키는 객체는 이미 지워진 상태, 또 한 그림이 제대로 교체되지도 않았는데 imagechanges 변수는 이미 증가
[ 예시 코드 ]
void PrettyMenu::changeBackground(istream& imgSrc)
{
Lock ml(&mutex);
delete bgImage;
++imageChanges;
bgImagd = new Image(imgSrc);
}
기본적인 보장
, 강력한 보장
, 예외불가 보장
중 하나를 반드시 제공해야 함 함수 동작 중 예외가 발생하면 실행 중인 프로그램에 관련된 모든 것들을 유효한 상태로 유지하겠다는 보장
어떤 객체나 자료구조도 더럽혀지지 않는다.
모든 객체의 상태는 내부적으로 일관성을 유지
프로그램의 상태가 정확히 어떠한지는 예측이 안 될 수도 있다.
- 함수의 제작자는 알겠지만 사용자는 알 수 없음
함수 동작 중 예외가 발생하면 프로그램의 상태를 절대로 변경하지 않겠다는 보장
강력한 보장이 이루어진 함수를 호출하는 것은 원자적인(atomic) 동작
- 예외가 발생하지 않으면 마무리까지 완벽하게 성공하고, 반대의 경우 함수가 아예 호출되지 않은 것 처럼 프로그램의 상태가 돌아간다는 면에서
기본적인 보장 함수보다 강력한 보장이 이루어진 함수가 쓰기에 더 편하다.
- 예측 가능한 프로그램의 상태가 두 개 뿐이기 때문
- 기본적인 보장의 경우 프로그램이 있을 수 있는 상태가 유효하기만 하면 어떤 상태든 될 수 있음
예외를 절대로 발생시키지 않겠다는 보장
- 약속한 동작은 언제나 끝까지 완수 하는...
기본제공 타입(int, 포인터 등)에 대한 모든 연산은 예외를 발생시키지 않게 되어있다.
예외에 안전한 코드를 만들기 위한 가장 기본적이며 핵심적인 요소
📢 어떤 예외도 발생시키지 않게끔 예외지정이 된 함수가 예외불가 보장을 제공한다고 생각하는 것은 잘못된 생각이다!
int doSomething() throw();
- doSomething이 절대로 예외를 발생시키지 않겠다는 것이 아닌 doSomething에서 예외 발생 시 매우 심각한 에러가 생긴것으로 판단, unexpected 함수가 호출 되어야 한다는 뜻
- 함수가 어떤 특성을 갖느냐 하는 부분은 '구현'이 결정하는 것
실용성이 있는 강력한 보장...?
예외 안전성 관점에서 봤을 때 가장 훌륭한 예외불가 보장...?📢 현실적으로는 기본적인 보장과 강력한 보장 중 하나를 고르게 된다!
- 예외를 발생시키는 함수를 호출하지 않고 C++의 C부분으로 부터 벗어나오긴 쉽지 않기 때문
[ 예시 코드 ]
class PrettyMenu
{
...
shared_ptr<Image> bgImage;
...
};
void PrettyMenu::changeBackground(istream& imgSrc)
{
Lock ml(&mutex);
bgImage.reset(new Image(imgSrc));
++imageChanges;
}
bgImage 데이터 멤버 타입을 기본제공 타입 Image* 에서 자원 관리 전담 포인터로 바꿔준다.
- 자원 누출 해결
함수 내 문장 재배치를 통해 배경 그림이 진짜로 바뀌기 전까지 imageChanges를 증가시키지 않도록 한다.
- 자료구조의 오염도 막았다!
delete 연산자는 reset 함수 안에 들어가 있으므로, reset이 불리지 않는 한 delete도 쓰일 일이 없다!
📢 뭔가 부족해...
📢 복사 후 맞바꾸기(copy and swap) 전략을 사용하면 강력한 예외 안전성을 보장할 수 있다!
[ 복사 후 맞바꾸기 예시 코드 ]
struct PMImpl
{
shared_ptr<Image> bgImage;
int imageChanges;
};
class PrettyMenu
{
...
private:
Mutex mutex;
shared_ptr<PMImpl> pImpl;
};
void PrettyMenu::changeBackground(istream& imgSrc)
{
using swap;
Lock ml(&mutex);
shared_ptr<PMImpl>pNew(new PMImpl(*pImpl));
pNew->bgImage.reset(new Image(imgSrc));
++pNew->imageChanges;
swap(pImpl, pNew);
}
복사 후 맞바꾸기 전략은 객체의 상태를 '전부 바꾸거나 혹은 안바꾸거나(all-or-nothing)' 방식으로 유지하려는 경우 효과적이다
하지만 함수 전체가 강력한 예외 안정성을 갖도록 보장하지는 않는다.
[ 예시 코드 ]
void someFunc()
{
...
f1();
f2();
...
}
- f1 혹은 f2에서 보장하는 예외 안전성이 '강력' 하지 못하면 someFunc 함수 역시 강력한 예외 안전성을 보장하기 힘들다.
- 어떤 함수가 제공하는 예외 안전성 보장 강도는, 그 함수가 내부적으로 호출하는 함수들이 제공하는 가장 약한 보장을 넘지 못한다.