[ Effective C++ ] 정리 모음집
" C++ 프로그래머의 필독서, 스콧 마이어스의 Effective C++ 를 읽고 내용 요약 / 정리 "
" 보통의 new를 쓰든, 예외불가 new를 쓰든 new 처리자의 동작 원리를 제대로 이해하자는 말은 변함이 없다! "
- set_new_handler 함수를 쓰면 메모리 할당 요청이 만족되지 못했을 때 호출되는 함수를 지정할 수 있다!
- 예외불가 new는 메모리 할당 자체에만 적용되기 때문에 영향력이 제한되어 있다. 이후에 호출되는 생성자에서는 얼마든지 예외를 던질 수 있다!
operator new
가 예외를 던지기 전 사용자 쪽에서 지정할 수 있는 에러 처리 함수를 우선적으로 호출하게 되있다. 그리고 이 에러를 가리켜 new 처리자 라고 한다 namespace std
{
typedef void(*new_handler)();
new_handler set_new_handler(new_handler p) throw();
}
메모리 고갈 상황을 처리할 함수를 사용자 쪽에서 지정할 수 있도록 표준 라이브러리에서 준비한 함수
- <new>
에 선언되어 있음
new_handler
는 함수의 포인터에 대해 typedef
를 걸어놓은 타입 동의어
- new_handler는 받는 것도, 반환하지도 않는다
set_new_handler
가 받는 매개 변수는(new_handler 타입) 메모리를 제대로 할당하지 못한 operator new
가 호출 할 함수의 포인터, 반환값은 set_new_handler
가 호출 되기 전 new 처리자로 쓰이던 함수의 포인터
사용할 수 있는 메모리를 더 많이 확보한다!
- operator new
가 시도하는 이후의 메모리 확보가 성공할 수 있도록 하자는 전략
- 대표적인 방법으로는 프로그램이 시작할 때 메모리 블록을 크게 하나 할당해 놓았다가 new 처리자가 가장 처음 호출 될 때 그 메모리를 쓸 수 있도록 허용하는 방법이 있다
(구현 방법은 이외에도 여러가지가 있다)
다른 new 처리자를 설치한다
- 현재의 new 처리자 안에서 set_new_handler
를 호출
new 처리자의 설치를 제거한다
- 즉, set_new_handler
에 널 포인터를 넘긴다
예외를 던진다
- bad_alloc
혹은 bad_alloc
에서 파생된 타입의 예외를 던진다
복귀하지 않는다
- abort()
혹은 exit()
를 호출
📢 반드시 위 동작 중 하나를 꼭 해줘야 한다!
class X
{
public:
static void outOfMemory();
...
};
class Y
{
public:
static void outOfMemory();
...
};
X* p1 = new X; // 메모리 할당이 실패했을 경우
// X::outOfMemory를 호출
Y* p2 = new Y; // 메모리 할당이 실패했을 경우
// Y::outOfMemory를 호출
위 처럼 동작하게 만들고 싶다면, 해당 클래스에서 자체 버전의 set_new_handler
및 operator new
를 제공하도록 만든다
- C++에는 특정 클래스만을 위한 할당 에러 처리자를 둘 수 있는 기능 같은 것이 없다. 따라서 직접 구현한다
자체 버전의 set_new_handler
class Widget
{
public:
static new_handler set_new_handler(new_handler p) throw();
static void* operator new(size_t size) throw(bad_alloc);
private:
static new_handler currentHandler;
};
new_handler Widget::currentHandler = 0; // 클래스 구현 파일에서
new_handler Widget::set_new_handler(new_handler p) throw()
{
new_hnadler oldHandler = currentHandler;
currentHandler = p;
return oldHandler;
}
operator new
class NewHandlerHolder
{
public:
explicit NewHandlerHolder(new_handler nh)
: handler(nh) {}
~NewHandlerHolder()
{ set_new_handler(handler); }
private:
new_handler handler;
NewHandlerHolder(const NewHandlerHolder&);
NewHandlerHolder&
operator=(const NewHandlerHolder&);
};
void* Widget::operator new(size_t size) throw(bad_alloc)
{
NewHandlerHolder
h(set_new_handler(currentHandler));
return ::operator new(size);
}
void outOfMem();
Widget::set_new_handler(outOfMem);
Widget* pw1 = new Widget;
string* ps = new string;
Widget::set_new_handler(0);
Widget* pw2 = new Widget;
📢 이 코드를 다른 클래스에서도 재사용 할 수 있도록 해보자!
template<typename T>
class NewHandlerSupport
{
public:
static new_handler set_new_handler(new_handler p) throw ();
static void* operator new(size_t size) throw(bad_alloc);
...
private:
static new_handler currentHandler;
};
template<typename T>
new_handler
NewHandlerSupport<T>::set_new_handler(new_handler p) throw()
{
new_handler oldHandler = currentHandler;
currentHandelr = p;
return oldHandler;
}
temaplate<typename T>
void* NewHandlerSupport<T>::operator new(size_t size) throw(bad_alloc)
{
NewHandlerHolder h(set_new_handler(currentHandler));
return ::operator new(size);
}
template<typename T>
new_handler NewHandlerSupport<T>::currentHandler = 0;
class Widget : public NewHandlerSupport<Widget>
{
...
};
class Widget { ... };
Widget* pw1 = new Widget;
if (pw1 == 0) ...
Widget* pw2 = new (nothrow) Widget;
if (pw2 == 0) ...
operator new
를 이용한 메모리 할당 실패 시 널 포인터를 반환 하다가 bad_alloc
예외를 던지도록 명세가 바뀌었으나, C++ 표준화 위원회는 '널 포인터 점검' 기반의 코드를 버리고 싶지 않아 전통적인 '할당 실패 시 널 반환' 으로 동작하는 대안적인 형태의 operator new
를 같이 내놓음