49. Understand the behavior of the new-handler
operator new가 사용자가 보낸 메모리 할당 요청을 맞춰주지 못한다면, 예외를 던지는데 이 예외를 던지기 전에 set_new_handler 함수를 쓰면 메모리 할당 요청이 만족되지 못했을 때 호출되는 함수를 지정할 수 있습니다.
// set_new_handler함수는 <new>에 선언되어 있습니다.
namespace std {
typedef void (*new_handler)(void);
new_handler set_new_handler(new_handler p) throw();
}
위 throw()는 이 함수는 어떤 예외도 던지지 않을 것을 의미합니다. 메모리할당 실패시 operator_new가 호출할 함수의 포인터는 set_new_handler 함수의 매개변수이며, 기존에 설정되어 있던 new 처리자는 set_new_handler 함수의 반환값입니다. 사용자가 부탁한 만큼의 메모리를 할당해 주지 못하면, operator new는 충분한 메모리를 찾아낼 때까지 new 처리자를 되풀이해서 호출합니다. 이와 같이 set_new_handler로 지정한 new 처리자는 다음 중 하나를 꼭 수행해야 합니다.
- 사용할 수 있는 메모리를 더 많이 확보합니다
operator new가 시도하는 이후의 메모리 확보가 성공할 수 있도록 하자는 전략입니다. 구현 방법은 여러가지가 있지만, 프로그램이 시작할 때 메모리 블록을 크게 하나 할당해 놓았다가 new 처리자가 가장 처음 호출될 때 그 메모리를 쓸 수 있도록 허용하는 방법이 그 예입니다. - 다른 new 처리자를 설치합니다
현재의 new 처리자가 더 이상 가용 메모리를 확보할 수 없다 해도, 이 경우 자기 몫까지 해 줄 다른 new 처리자의 존재를 알고 있을 가능성도 있겠지요. 만약 그렇다면 현재의 new 처리자 안에서 set_new_handler를 호출하여 다른 new 처리자를 설치할 수 있습니다. operator new 함수가 다시 new 처리자를 호출할 때가 되면, 가장 마지막에 설치된 new 처리자가 호출되는 것입니다. - new 처리자의 설치를 제거합니다
다시 말해, set_new_handler에 널 포인터를 넘깁니다. new 처리자가 설치된 것이 없으면, operator new 는 메모리 할당이 실패했을 때 예외를 던지게 됩니다. - 예외를 던집니다
bad_alloc 혹은 bad_alloc에서 파생된 타입의 예외를 던집니다. operator new에는 이쪽 종류의 에러를 받아서 처리하는 부분이 없기 때문에, 이 예외는 메모리 할당을 요청한 원래의 위치로 전파됩니다. - 복귀하지 않습니다
대게 abort 혹은 exit를 호출합니다.
할당된 객체의 클래스 타입에 따라 메모리 할당 실패에 대한 처리를 다르게 가져 가고 싶을 때는 해당 클래스에서 자체 버전의 set_new_handler 및 operator new를 제공하도록 만들어 주어야 합니다. 먼저 아래와 같이 new_handler를 가리키는 정적 멤버 데이터를 선언하여 new_handler를 보관하고, Widget에서 set_new_handler 함수는 자신에게 넘어온 포인터를 저장하고 이전의 포인터를 반환하는 코드를 짭니다. 이후에 operator new의 역할만이 남습니다.
class Widget {
public:
static std::new_handler set_new_handler(std::new_handler p) throw();
static void * operator new(std::size_t size) throw(std::bad_alloc);
private:
static std::new_handler currentHandler;
};
std::new_handler currentHandler = 0; // NULL로 초기화합니다.
std::new_handler Widget::set_new_handler(std::new_handler p) throw()
{
std::new_handler oldHandler = currentHandler;
currentHandler = p;
return oldHandler;
}
- 표준 set_new_handler에 Widget의 new처리자를 넘긴다. 즉 전역 new처리자로 Widget new처리자를 설치합니다.
- 전역 operator_new로 메모리를 할당한다. 할당이 실패하면 설치한 Widget new 처리자가 호출되는데 이것마저도 실패시 예외를 발생시켜, new 처리자를 old new처리자로 교체하고 예외를 던집니다.
- 할당에 성공한 경우에는 할당된 메모리를 반환하고, 이와 동시에 전역 new처리자를 자동 복원하게 설계합니다.
이는 아래와 같이 구현됩니다.
class NewHandlerHolder {
public:
explicit NewHandlerHolder(std::new_handler nh) // 현재의 new 처리자를
: handler(nh) {} // 획득합니다.
~NewHandlerHolder(void) { // 이것을 해제합니다.
std::set_new_handler(handler);
}
private:
std::new_handler handler; // 이것을 기억해 둡니다.
NewHandlerHolder(const NewHandlerHolder&); // 복사를 막기 위한 부분
NewHandlerHolder& // (항목 14를 참고하세요)
operator=(const NewHandlerHolder&);
};
void * Widget::operator new(std::size_t size) throw(std::bad_alloc)
{
NewHandlerHolder // Widget의 new 처리자를
h(std::set_new_handler(currentHandler)); // 설치합니다.
return ::operator new(size); // 메모리를 할당하거나
// 할당이 실패하면 예외를 던집니다.
} // 이전의 전역 new 처리자가
// 자동으로 복원됩니다.
// Widget클래스를 사용하는 쪽에서의 예제
void outOfMem(void); // Widget 객체에 대한 메모리 할당이
// 실패했을 때 호출될 함수의 선언
Widget::set_new_handler(outOfMem); // Widget의 new 처리자 함수로서
// outOfMem을 설치합니다.
Widget *pw1 = new Widget; // 메모리 할당이 실패하면
// outOfMem이 호출됩니다.
std::string *ps = new std::string; // 메모리 할당이 실패하면
// 전역 new 처리자 함수가
// (있으면) 호출됩니다.
Widget::set_new_handler(0); // Widget 클래스만을 위한
// new 처리자 함수가 아무것도 없도록
// 합니다(즉, null로 설정합니다).
Widget *pw2 = new Widget; // 메모리 할당이 실패하면 이제는
// 예외를 바로 던집니다.(Widget
// 클래스를 위한 new 처리자 함수가
// 없습니다.
위의 예제를 여러 다른 클래스에 적용해도 비슷한 코드가 만들어질 것 같습니다. 따라서 이 경우 "믹스인(mixin) 양식"의 기본 클래스를 만드는 것을 추천합니다.
template <typename T> // 클래스별 set_new_handler를
class NewHandlerSupport { // 지원하는 "믹스인 양식"의
public: // 기본 클래스
static std::new_handler set_new_handler(std::new_handler p) throw();
static void * operator new(std::size_t size) throw(std::bad_alloc);
... // operator new의 다른 버전들을
// 이 자리에 둡니다. 항목 52를 참고하세요.
private:
static std::new_handler currentHandler;
};
template <typename T>
std::new_handler
NewHandlerSupport<T>::set_new_handler(std::new_handler p) throw()
{
std::new_handler oldHandler = currentHandler;
currentHandler = p;
return oldHandler;
}
template <typename T>
void* NewHandlerSupport<T>::operator new(std::size_t size)
throw(std::bad_alloc);
{
NewHandlerHolder h(std::set_new_handler(currentHandler));
return ::operator new(size);
}
// 클래스별로 만들어지는 currentHandler 멤버를 널로 초기화합니다.
template <typename T>
std::new_handler NewHandlerSupport<T>::currentHandler = 0;
예외불가(nothrow) new는 영향력이 제한되어 있습니다. 메모리 할당 자체에만 적용되기 때문입니다. 이후에 호출되는 생성자에서는 얼마든지 예외를 던질 수 있습니다.
class Widget { ... };
Widget *pw1 = new Widget; // 할당이 실패하면
// bad_alloc 예외를 던집니다.
if (pw1 == 0) ... // 이 점검 코드는 꼭 실패합니다.
Widget *pw2 = new(std::nothrow) Widget; // Widget을 할당하다 실패하면
// 0(널)을 반환합니다.
if (pw2 == 0) ... // 이 점검 코드는 성공할 수 있습니다.
이제 Widget 클래스에 set_new_handler 기능을 추가하는 것은 별로 어려워지지 않게 됩니다. 그저 NewHandlerSupport<Widget>로부터 상속만 받으면 끝이거든요.
class Widget: public NewHandlerSupport<Widget> {
... // 이전과 같지만, 이제는 set_new_handler 혹은
}; // operator new에 대한 선언문이 빠져 있습니다.
여기서 new가 예외를 던지지 않는다 하더라도 Widget의 생성자에서 예외를 던질 수도 있습니다. 즉 예외불가 new는 그때 호출되는 operator new에서만 예외가 발생되지 않도록 보장할 뿐, "new(std::nothrow) Widget" 등의 표현식에서 예외가 나오지 않게 막아 준다는 이야기는 아닙니다. 이러한 이유 때문에 십중팔구는 예외불가 new를 필요로 할 일이 없을 것입니다.
이것만은 잊지 말자!
- set_new_handler 함수를 쓰면 메모리 할당 요청이 만족되지 못했을 때 호출되는 함수 지정 가능
- 예외불가 new는 메모리 할당 자체에만 적용되기 때문에 영향력이 제한되어 있어, 이후에 호출되는 생성자에서는 얼마든지 예외를 던질 수 있음.
50. Understand when it makes sense to replace new and delete
스스로 사용자 정의 new와 delete를 만드는 데에는 보통 다음과 같은 목적을 가집니다.
- 잘못된 힙 사용을 탐지하기 위해
new한 메모리에 delete를 하는 것을 잊는다던지, delete한 메모리를 한 번 더 delete한다던지, 데이터 오버런 및 데이터 언더런과 같은 실수들을 잡아내기 위해 - 효율을 향상시키기 위해
어디든 적합한 코드보다도 필요한 코드를 만들어 더 좋은 성능을 내기 위해 - 동적 할당 메모리의 실제 사용에 관한 통계 정보를 수집하기 위해
여러분이 만드는 소프트웨어가 동적 메모리를 어떻게 사용하는지에 대한 정보를 수집하기 위하여 안에 표시 및 Log를 남기기 위해 - 할당 및 해제 속력을 높이기 위해
예를 들어 여러분이 만들 응용프로그램은 단일 스레드로 동작하는데 컴파일러에서 기본으로 제공하는 메모리 관리 루틴이 다중 스레드에 맞게 만들어져 있다면, 스레드 안전성이 없는 할당자를 여러분이 직접 만들어 씀으로써 성능 향상을 맛볼 수 있음. - 기본 메모리 관리자의 공간 오버헤드를 줄이기 위해
범용 메모리 관리자는 사용자 정의 버전과 비교해서 속력이 느린 경우도 많은데다가 메모리도 많이 잡아먹는 사례가 허다합니다. 할당된 각각의 메모리 블록에 대해 전체적으로 지우는 부담이 꽤 되기 때문입니다. 크기가 작은 객체에 대한 튜닝된 할당자를 사용하면 이러한 오버헤드를 실질적으로 제거할 수 있습니다. - 적당히 타협한 기본 할당자의 바이트 정렬 동작을 보장하기 위해
예를 들어 x86 아키텍쳐에서는 double이 8바이트 단위로 정렬되어 있을 때 읽기·쓰기 속도가 가장 빠릅니다. 하지만 일부 컴파일러 중에는 operator new 함수가 double에 대한 동적 할당 시에 8바이트 정렬을 보장하지 않는 것들이 있다는 슬픈 소식이 나돌고 있습니다. 이런 경우, 기본제공 operator new 대신에 8바이트 정렬을 보장하는 사용자 정의 버전으로 바꿈으로써 프로그램 수행 성능을 확 끌어올릴 수 있습니다. - 임의의 관계를 맺고 있는 객체들을 한 군데에 나란히 모아 놓기 위해
한 프로그램에서 특정 자료구조 몇 개가 대게 한 번에 동시에 쓰이고 있습니다. 이들에 대해서 페이지 부재(page fault)발생 횟수를 최소화하고 싶은 경우, 해당 자료구조를 담을 별도의 힙을 생성함으로써 이들이 가능한 한 적은 페이지를 차지하도록 하면 상당히 좋은 효과를 볼 수 있겠지요. 이러한 메모리 군집화는 위치지정(placement) new 및 위치지정 delete를 통해 쉽게 구현할 수 있습니다. - 그때그때 원하는 동작을 수행하도록 하기 위해
기본제공 new와 delete가 해 주지 못하는 일을 operator new 및 operator delete가 해 주길 바랄 때가 있게 마련입니다. 메모리 할당과 해제를 공유 메모리에다 하고 싶은데 공유 메모리를 조작하는 일은 C API로밖에 할 수 없을 때가 한 가지 예입니다. 이 때 사용자 정의 버전 위치지정 new와 위치지정 delete를 만드는 것입니다. 또 다른 예로는 응용프로그램 데이터의 보안 강화를 위해 해제한 메모리 블록에 0을 덮어 쓰는 사용자 정의 operator delete를 만드는 경우도 생각해 볼 수 있습니다.
이것만은 잊지 말자!
- 개발자가 스스로 사용자 정의 new 및 delete를 작성하는 데는 여러 가지 나름대로 타당한 이유가 존재합니다.
51. Adhere to convention when writing new and delete
[operator new 함수의 관례]
- 제대로된 반환값
요청 메모리를 마련할 수 있는 경우 - 포인터 값 반환
요청 메모리를 마련할 수 없는 경우 - bad_alloc 타입의 예외 - 가용 메모리가 부족한 경우 new처리자를 호출
- 크기가 없는 메로리 요청에 대한 대비책
- 기본 형태의 new가 가려지지 않도록 구현
operator new는 메모리 할당이 실패할 때 마다 new 처리자 함수를 호출하는 식으로 2회 이상 시도합니다. operator new가 예외를 던지는 경우는 오직 new 처리자 함수에 대한 포인터가 널일 때 뿐입니다. 이를 구현하면 다음과 같이 됩니다.
void * operator new(std::size_t size) throw(std::bad_alloc)
{ // 여러분의 operator new 함수는 다른
// 매개변수를 추가로 가질 수 있습니다.
using namespace std;
if (size == 0) { // 0바이트 요청이 들어오면
size = 1; // 이것을 1바이트 요구로
} // 간주하고 처리합니다.
while (true) {
size바이트를 할당해 봅니다;
if ( 할당이 성공했음 )
return ( 할당된 메모리에 대한 포인터 );
// 할당이 실패했을 경우, 현재의 new 처리자 함수가
// 어느 것으로 설정되어 있는지 찾아냅니다(아래를 보세요).
new_handler globalHandler = set_new_handler(0);
set_new_handler(globalHandler);
if (globalHandler) (*globalHandler)();
else throw std::bad_alloc();
}
}
while true에서 무한 루프를 돌면서 루프를 벗어나오기 위해서는 메모리 할당이 성공하거나, new 처리자 함수 쪽에서 처리를 해줘야하는데, new 처리자 함수에서 직무유기를 해 버릴 경우, operator new의 내부 루프는 절대로 스스로 끝나지 않습니다.
operator new 멤버 함수는 파생 클로스 쪽으로 상속이 되는 함수인데, 만약 어떤 X라는 클래스를 위한 operator new라는 함수가 존재하고, 이 함수의 동작은 크기가 sizeof(X)에 맞춰져 있다고 합시다. 근데 만약 X를 상속받은 자식 클래스에서 상속받은 operator new를 사용하게 되는 순간 크기가 다르기 때문에 이에 대한 처리를 해주어야 합니다. 흔한 방법으로는 표준 operator new를 호출하도록 합니다.
class Base {
static void *operator new(std::size_t size) throw(std::bad_alloc)
...
};
class Derived: public Base { ... };
Derived *p = new Derived;
void *Base::operator new(std::size_t size) throw(std::bad_alloc)
{
if (size != sizeof(Base))
return ::operator new(size);
...
};
만약 배열에 대한 메모리 할당을 클래스 전용 방식으로 하고 싶다면 operator new[]를 구현해야합니다. 이 때 주의할 점은 operator new[] 안에서 해 줄 일은 단순히 원시 메모리의 덩어리를 할당하는 것밖엔 없다는 것입니다. 왜냐하면 상속 때문에, 파생 클래스 객체의 배열을 할당하는 데 기본 클래스의 operator new[] 함수가 호출될 수 있는데, 그렇기 때문에 Base::operator new[] 안에서조차 배열에 들어가는 객체 하나의 크기가 sizeof(Base)라는 가정을 할 수 없습니다. 이 말을 풀이해 보면, Base::operator new[]에서 할당한 배열 메모리에 들어가는 객체의 개수를 (요구된 바이트 수 / sizeof(Base))로 계산할 수 없다는 뜻입니다. 또한, operator new[]에 넘어가는 size_t 타입의 인자는 객체들을 담기에 딱 맞는 메모리 양보다 더 많게 설정되어 있을 수도 있습니다. 왜냐하면 동적으로 할당된 배열에는 배열 원소의 개수를 담기 위한 자투리 공간이 추가로 들어가기 때문입니다.
operator delete 함수는 널 포인터가 들어왔을 때 아무 일도 하지 않아야 합니다.
void operator delete(void * rawMemory) throw()
{
if (rawMemory == 0) return; // 널 포인터가 delete되려고 할 경우에는
// 아무것도 하지 않게 합니다.
rawMemory 가 가리키는 메모리를 해제합니다;
}
클래스 전용 버전의 경우에는 예정 크기보다 더 큰 블록을 처리해야 합니다.
class Base { // 이전과 같으나, 지금은
public: // operator delete가 선언된 상태
static void * operator new(std::size_t size) throw(std::bad_alloc);
static void operator delete(void *rawMemory, std::size_t size) throw();
...
};
void Base::operator delete(void *rawMemory, std::size_t size) throw()
{
if (rawMemory == 0) return; // 널 포인터에 대한 점검
if (size != sizeof(Base)) { // 크기가 "틀린" 경우
::operator delete(rawMemory); // 표준 operator delete가
return; // 메모리 삭제 요청을 맡도록 합니다.
}
rawMemory가 가리키는 메모리를 해제합니다;
return;
}
중요한 점을 하나 더 말씀드리자면, 가상 소멸자가 없는 기본 클래스로부터 파생된 클래스의 객체를 삭제하려고 할 경우에는 operator delete로 C++가 넘기는 size_t 값이 엉터리일 수 있습니다. 이것만으로도 기본 클래스에 가상 소멸자를 꼭 두어야 하는 충분한 이유가 선다고 말씀드릴 수 있을 것 같습니다. (기본 클래스에서 가상 소멸자를 빼먹으면 operator delete 함수가 똑바로 동작하지 않을 수 있다는 사실을 잊지 마세요)
이것만은 잊지 말자!
- 관례적으로, operator new 함수는 메모리 할당을 반복해서 시도하는 무한루프를 가져야 하고, 메모리 할당 요구를 만족시킬 수 없을 때 new 처리자를 호출해야 하며, 0바이트에 대한 대책도 있어야 합니다. 클래스 전용 버전은 자신이 할당하기로 예정된 크기보다 더 큰 메모리 블록에 대한 요구도 처리해야 합니다.
- operator delete 함수는 널 포인터가 들어왔을 때 아무 일도 하지 않아야 합니다. 클래스 전용 버전의 경우에는 예정 크기보다 더 큰 블록을 처리해야 합니다.
52. Write placement delete if you write placement new
operator new 함수의 위치지정(placement) 버전을 만들 때는, 이 함수와 짝을 이루는 위치지정 버전의 operator delete 함수도 꼭 만들어 주세요. 이 일을 빼먹었다가는, 찾아내기도 힘들며 또 생겼다가 안 생겼다 하는 메모리 누출 현상을 경험하게 됩니다.
Widget *pw = new Widget;
위의 코드에서는 먼저 operator new 함수가 불려 메모리를 할당한 뒤 Widget의 생성자가 호출됩니다. 그런데 Widget의 생성자에서 예외가 발생한 경우 프로그래머는 메모리 누수를 해결할 수 없습니다. 동적 할당 받은 메모리의 시작 주소가 pw에 담기기도 전에 예외가 던져지기 때문이죠. 이 경우 메모리의 해제는 C++ 런타임 시스템께서 맡이 주시게 됩니다. 하지만 C++ 런타임 시스템이 알아서 메모리를 해제하려면 new와 짝이 맞는 delete를 알아야 합니다. 일반적인 경우 이것은 큰 문제가 되지 않습니다. 왜냐하면 기본형 operator new는 기본형 operator delet와 짝을 맞추기 때문입니다.
void operator delete(void *rawMemory) throw(); // 전역 유효범위에서의 기본형 시그너처
void operator delete(void *rawMemory, // 클래스 유효범위에서의 전형적인 기본형 시그니처
std::size_t size) throw();
따라서 표준 형태의 new 및 delete만 사용하는 한, 런타임 시스템은 new의 동작을 되돌릴 방법을 알고 있는 delete을 문제없이 찾아낼 수 있습니다. 하지만 operator new의 기본형이 아닌 형태를 선언하기 시작하면 new와 delete짝을 맞추는 데 문제가 뽀송뽀송 피어나게 됩니다. 비기본형이란 바로 다른 매개변수를 추가로 갖는 operator new를 뜻합니다. (이런 operator new를 위치지정 (placement) new 라고 합니다.)
operator new를 호출하는 데 cerr을 ostream 인자로 넘기는데, 이 때 Widget 생성자에서 예외가 발생하면 메모리가 누출됩니다.
class Widget {
public:
...
static void* operator new(std::size_t size, // 비표준 형태의
std::ostream& logStream) // operator new
throw(std::bad_alloc);
static void operator delete(void *pMemory // 클래스 전용
size_t size) throw(); // operator delete의
// 표준 형태
...
};
Widget *pw = new(std::cerr) Widget;
위에서 호출된 new는 ostream& 타입의 매개변수를 추가로 받아들이므로, 이것과 짝을 이루는 operator delete 역시 똑같은 시그너처를 가진 것이 마련되어 있어야 합니다.
void operator delete(void *, std::ostream&) throw();
이러한 operator delete를 위치지정 delete라고 합니다. 따라서 예제에서 위치지정 delete를 추가해 주면 메모리 누출 위험이 사라집니다.
class Widget {
public:
...
static void* operator new(std::size_t size, std::ostream& logStream)
throw(std::bad_alloc);
static void operator delete(void *pMemory) throw();
static void operator delete(void *pMemory, std::ostream& logStream)
throw();
...
};
Widget *pw = new(std::cerr) Widget; // 이전과 같은 코드이지만
// 메모리 누출이 없습니다.
delete pw; // 기본형의 operator delete가 호출됩니다
일반적인 경우에는 위치지정 delete 대신 기본형의 operator delete 가 호출됩니다.
new 및 delete의 위치지정 버전을 선언할 때는, 의도한 바도 아닌데 이들의 표준 버전이 가려지는 일이 생기지 않도록 주의해 주세요.
class Base {
public:
...
static void* operator new(std::size_t size, // 이 new가
std::ostream& logStream) // 표준 형태의
throw(std::bad_alloc); // 전역 new를 가립니다.
...
};
Base *pb = new Base; // 에러! 표준 형태의 전역
// operator new가 가려지거든요.
Base *pb = new(std::cerr) Base; // 이건 문제없습니다. Base의
// 위치지정 new를 호출합니다.
파생 클래스는 전역 operator new는 물론이고 자신이 상속받는 기본 클래스의 operator new까지 가려 버립니다.
class Derived: public Base { // 위의 Base로부터 상속받은 클래스
public:
...
static void* operator new(std::size_t size) // 기본형 new를 클래스 전용으로
throw(std::bad_alloc); // 다시 선언합니다.
...
};
Derived *pd = new(std::cerr) Derived; // 에러! Base의 위치지정
// new가 가려져 있기 때문입니다.
Derived *pd = new Derived; // 문제없습니다. Derived의
// operator new를 호출합니다.
C++가 전역 유효 범위에서 제공하는 operator new의 형태는 다음 세 가지가 표준입니다.
void* operator new(std::size_t) throw(std::bad_alloc); // 기본형 new
void* operator new(std::size_t, void*) throw(); // 위치지정 new
void* operator new(std::size_t, // 예외불가 new
const std::nothrow_t&) throw(); // (항목 49 참조)
어떤 형태든 간에 operator new가 클래스 안에 선언되는 순간, 앞의 예제처럼 위의 표준 형태들이 몽땅 가려지는 것입니다. 다음과 같은 한 가지 해결책이 있습니다. 기본 클래스 하나를 만들고, 이 안에 new 및 delete의 기본 형태를 전부 넣어두십시오.
class StandardNewDeleteForms {
public:
// 기본형 new/delete
static void* operator new(std::size_t size) throw(std::bad_alloc)
{ return ::operator new(size); }
static void operator delete(void *pMemory) throw()
{ ::operator delete(pMemory); }
// 위치지정 new/delete
static void* operator new(std::size_t size, void *ptr) throw()
{ return ::operator new(size, ptr); }
static void operator delete(void *pMemory, void *ptr) throw()
{ return ::operator delete(pMemory, prt); }
// 예외불가 new/delete
static void* operator new(std::size_t,
const std::nothrow_t& nt) throw()
{ return ::operator new(size, nt); }
static void operator delete(void *pMemory,
const std::nothrow_t&) throw()
{ ::operator delete(pMemory); }
};
표준 형태에 덧붙여 사용자 정의 형태를 추가하고 싶다면, 이제는 이 기본 클래스를 축으로 넓혀 가면 됩니다. 상속과 using 선언을 2단 콤보로 사용해서 표준 형태를 파생 클래스 쪽으로 끌어와 외부에서 사용할 수 있게 만든 후에, 원하는 사용자 정의 형태를 선언해 주세요.
class Widget: public StandardNewDeleteForms { // 표준 형태를 물려받습니다.
public:
using StandardNewDeleteForms::operator new; // 표준 형태가 (Widget
using StandardNewDeleteForms::operator delete; // 내부에) 보이도록 만듭니다.
static void* operator new(std::size_t size, // 사용자 정의 위치지정
std::ostream& logStream) // new를 추가합니다.
throw(std::bad_alloc);
static void operator delete(void *pMemory, // 앞의 것과 짝이 되는
std::ostream& logStream) // 위치 지정 delete를
throw(); // 추가합니다.
...
};
이것만은 잊지 말자!
- operator new 함수의 위치지정 버전을 만들 때는, 이 함수와 짝을 이루는 위치지정 버전의 operator delete 함수도 꼭 만들어 주세요. 이를 빼먹으면 찾아내기도 힘들며 또 생겼다가 안 생겼다 하는 메모리 누출 현상을 경험하게 됩니다.
- new 및 delete의 위치지정 버전을 선언할 때는, 의도한 바도 아닌데 이들의 표준 버전이 가려지는 일이 생기지 않도록 주의해 주세요.
'C++공부 > Effective C++' 카테고리의 다른 글
Chapter 7. Templates and Generic Programming (0) | 2022.05.21 |
---|---|
Chapter 6: Inheritance and Object-Oriented Design (0) | 2022.05.21 |
Chapter 5. Implementations (0) | 2022.05.20 |
Chapter 4. Designs and Declarations (0) | 2022.05.19 |
Chapter 3. Resource Management (0) | 2022.05.19 |