3. Item 49: new 처리자
• new 처리자
• 메모리 할당이 제대로 이루어지지 않은 경우 예외처리!
• 예외처리에 앞서 사용자의 커스텀 예외처리자, new-handler가 호출됨
• set_new_handler를 통해서 설정가능.
namespace std {
typedef void (*new_handler) ();
new_handler set_new_handler(new_handler p) throw ();
}
4. Item 49: new 처리자
• new 처리자에서 해줄 수 있는 일들
• 메모리 추가 확보
• 다른 new 처리자를 찾아서 호출
• NULL – 즉 default 예외처리
• 커스텀 예외처리 ( bad_alloc )
5. Item 49: new 처리자
• 클래스 별로 별도의 new handler를 사용하고 싶다면
• c++ 자체기능은 없으니 직접 만들어야함.
• 클래스 커스텀 set_new_handler
• new 처리자(함수)를 받아오는 역할
• operator new 오버라이딩
• 메모리 할당이 실패한 경우 클래스 자체 new-handler를 호출
7. Item 49: new 처리자
• operator new
• 표준 set_new_handler 함수에 Widget의 new_handler를 넘겨준다.
• new_handler 변경한 채로 new 호출, 종료 후 new_handler 복원
• 실패 시 bad_alloc 쓰로우 & new_handler 복원
• 위 작업에서 new_handler 복원 처리를 별도의 자원관리자를 통해서 만
들면 쉽게 구현가능
• new_handler 관리자 NewHandlerHolder를 만들자.
8. Item 49: new 처리자
• class NewHandlerHolder{
public:
explicit NewHandlerHolder(new_handler nh)
: handler (nh) { } //생성자에서 new_handler 받기
~NewHandlerHolder() //소멸자에서 set_new_handler호출
{ set_new_handler(handler); }
private:
new_handler handler;
}
9. Item 49: new 처리자
• Widget의 operator new
void* Widget::operator new(size_t size) throw(bad_alloc)
{
NewHandlerHolder h(set_new_handler(currentHolder));
// 기존의 newHandler를 저장한다.
return ::operator new(size); // 할당 작업 진행
//실패해도 NewHandlerHolder의 소멸자 호출 handler 복원
}
10. Item 49: new 처리자
• 템플릿화 해서 상속받아 사용할 수 있게 하자.
template<typename T>
class NewHandlerSupport {
public :
static new_handler set_new_handler( new_handler p );
static void* operator new(size_t size) throw(bad_alloc);
}
11. Item 49: new 처리자
• 상속 받아서 사용하는 방법
• class Widget : public NewHandlerSupport<Widget>
• template에서 T가 한번도 안쓰이는 데?
• 클래스별로 별도의 NewHandlerSupport가 필요함
• 클래스마다 각각 currentHandler가 다를 것이므로.
• T를 받아서 별도의 NewHandlerSupport를 만들어주기 위함
• CRTP라고 하는 자주 사용되는 패턴
12. Item 49: new 처리자
• 예외 불가 new ( 추가 이슈 )
• 예전에 할당실패 시 bad_alloc Throw말고 그냥 NULL을 리턴했다.
• 지금도 예외불가 new 하는 방법은 존재
new (std::nothrow) Widget ;
• 하지만 예외불가는 오직 Widget 공간 할당에 한해서만 적용된다.
• 내부 생성자에서 다시 new를 해서 실패한 예외는 적용 안됨
• 어쨌든 new_handler의 동작 구조를 잘 알아두자.
14. Item 50: 적절한 operator new/delete
• 왜 new와 delete를 오버라이딩 하는가
• 잘못된 힙 사용 방지
• delete 두 번
• 할당 영역을 넘어선 접근 ( 오버런, 언더런 )
• 통계 수집
• 할당된 메모리 블록의 크기 / 사용기간 분포
• 메모리 사용패턴
15. Item 50: 적절한 operator new/delete
• 왜 new와 delete를 오버라이딩 하는가
• 할당 / 해제 성능 효율
• 특정한 조건에 할당/해제 작업에 최적화 할 수 있다.
• 고정크기의 객체만 만드는 경우
• 싱글 쓰레드 환경이 보장된 경우
• 공간 오버헤드를 최소화
• 안정적인 사용을 위해 다소 불필요한 메모리 공간(관리 영역) 발생
• 별도로 관리자가 따로 있다면 관리영역을 많이 줄일 수 있다.
16. Item 50: 적절한 operator new/delete
• 왜 new와 delete를 오버라이딩 하는가
• 바이트 정렬 보장
• 시스템 아키텍처를 최대한 활용하기 위해 메모리를 8바이트 단위 정렬할 수 있다.
• 기존 컴파일러가 이를 보장하지 않는 경우가 종종 있기 때문에 별도로 제작
• 같은 자료구조, 관계 있는 객체들의 물리적 locality 증대
• 객체를 별도로 할당을 관리하여 메모리의 locality를 증대시킬 수 있다.
• new / delete 시 별도의 동작 추가
18. Item 50: 적절한 operator new/delete
• 위 operator new의 문제점
• new_handler 호출 루프가 없음 (item 51에서 자세히)
• 바이트 정렬
• malloc해서 받은 값에서 int만큼 offset 이동시킨 주소를 리턴 하고 있음
• 메모리가 제대로 정렬된 상태가 아닐 가능성!
• 2^n 크기로 데이터들을 정렬해야 최적화된다.
• 아키텍처에 따라서 잘못된 동작을 야기할 수 도 있다.
• 멀티 쓰레드에 대한 개념 전혀 없음
19. Item 50: 적절한 operator new/delete
• 가능하면 전문가에게 맡기자
• 메모리 관리자를 잘 만들기란 쉽지 않은 일이다.
• 일반적인 경우 컴파일러의 기본 operator new/delete는 훌륭하다.
• 특별한 경우 라이브러리의 도움을 받자.
• boost::Pool : 크기가 작은 객체를 많이 할당/해제하는 경우
• 정렬 이슈/ 멀티 쓰레드 이슈가 잘 정리되어 있다.
• 직접 만들기 전에 라이브러리 코드를 보고 배우는 것도 도움이 될 것이다.
21. Item 51: operator new/delete의 convention
• operator new에서 convention
• 확실한 반환 값
• 요청된 메모리의 포인터를 반환
• 0 size 메모리 요청 대비
• 할당 실패시 우선 new 처리자 함수를 호출
• 그래도 안되면 bad_alloc throw
• 기존 new를 완전히 덮어 쓰지 말것 (item 52에서 자세히)
23. Item 51: operator new/delete의 convention
• 클래스 멤버변수 operator new의 convention
• operator new 또한 상속된다.
• 상속받은 클래스가 Base의 operator new를 호출했을 경우를 체크하자.
• item 50의 경우처럼 특수한 new를 만들었을 경우 기본 new를 호출할 수 있게
void* Base::operator new (size_t size) throw (bad_alloc) {
if( size != sizeof(Base) ) //틀린 크기가 들어오면 기존의 ::operator new사용
return ::operator new( size );
…
}
• 클래스의 크기는 최소 1이므로 0바이트 체크까지 되는 코드임
24. Item 51: operator new/delete의 convention
• operator new[] 에서 convention
• 단순히 주어진 사이즈의 메모리 덩어리를 할당하면 된다.
• 배열 정보를 함부로 추론하려 하지마라
• 몇 개의 객체가 들어올지 예측할 수 없다.
• new를 호출할 파생 클래스 객체의 사이즈를 알 수 없다.
• 안다고 해도 new [] 는 실제 객체 * 배열 길이 보다 더 큰 사이즈를 할당한다.
• item 16에서 언급한 헤더정보가 추가된다.
25. Item 51: operator new/delete의 convention
• operator delete에서 convention
• null포인터 exception만 조심하면 된다.
void operator delete( void* rawMemory ) throw ()
{
if ( rawMemory == NULL ) return; //NULL delete 방지
//메모리 해제 작업
…
}
26. Item 51: operator new/delete의 convention
• 클래스 멤버변수 operator delete 에서 convention
• 상속받은 클래스가 BASE의 operator delete를 호출하는 경우를 체크하자.
void Base::operator delete ( void* rawMemory, size_t size ) throw ()
{
if ( rawMemory == 0 ) return; //NULL 체크
if ( size != sizeof( Base ) ) {
::operator delete( rawMemory );
return;
}
//메모리 해제 작업
…
}
28. Item 52: 위치지정 new / delete
• new 호출의 문제점
• Widget* pw = new Widget();
1. 메모리 할당을 위해 operater new 호출
2. Widget의 기본생성자 호출
• 메모리 할당은 잘 되었는데 Widget 생성자에서 exception?
• 런타임 시스템에서 operator new와 짝이 되는 operator delete를 호출해준다.
• 기본형의 경우는 문제없이 Pair로 잘 구성되어 있다.
• void operator new(size_t) throw (bad_alloc);
• void operator delete(void* rawMemory) throw( );
• 기본형이 아닌 operator new가 문제의 시작 (무엇이 Pair인가?)
29. Item 52: 위치지정 new / delete
• 기본형이 아닌 operator new (placement new)
• 할당시 log를 찍는 operator new 와 그 짝 delete
• void* operator new ( size_t size, ostream& logStream ) throw ( bad_alloc )
• void operator delete ( void* pMemory, size_t size) throw ( );
• 매개변수를 따로 받는 operator new 를 위치지정 new라고 한다.
• 자주 사용되던 미리 할당할 메모리 위치를 매개변수로 받는 new에서 시작
void operator new ( size_t size, void* pMemory) throw ( );
• ex : vector의 미사용 공간에 원소 객체를 할당-생성 할 때
30. Item 52: 위치지정 new / delete
• 런타임 시스템에서 Pair가 되는 operator delete를 찾는 법
• 추가 매개변수의 개수 및 타입이 똑같은 operator delete를 Pair로
• 위 로그 찍는 operator new/delete 예제에서는 서로 짝이 안 맞는다.
• 새로운 operator delete 선언
void operator delete( void* pMemory, ostream& logStream ) throw ();
• 이런 operator delete를 위치지정 delete라고 부른다.
31. Item 52: 위치지정 new / delete
• 만약 예외처리 없이 일반적으로 delete를 호출한다면?
• 런타임 시스템은 기본형의 operator delete를 호출한다.
• 위치지정 delete는 위치지정 new의 Pair가 필요할 때만 호출된다.
• 따라서 표준형태의 operator delete는 기본으로 항상 마련해야 한다.
• 추가적으로 위치지정 new를 썼다면 위치지정 delete도 만들어야 한다.
32. Item 52: 위치지정 new / delete
• operator new의 오버라이딩 문제 (이름 가림 이슈)
• 이름 문제의 디테일은 item 33 참조
• 전용의 operator new가 다른 operator new들을 가리지 않도록!
• Widget 클래스에서 위치지정 new만 선언/정의했다면?
Widget* pw = new Widget(); //error! 전역 operator new를 가린다.
• 전역에서 유효한 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, const std::nothrow_t&) throw(); //예외 불가 new
33. Item 52: 위치지정 new / delete
• 클래스에서 operator new를 정의하려면
• 모든 형태의 operator new및 pair가 되는 operator delete 정의필요
• 미리 operator들을 부를 수 있는 인터페이스 클래스를 만들어두자.
class StandardNewDeleteForms { //모든 종류의 operator new들을 선언한 인터페이스
public:
static void* operator new(std::size_t size) throw(std::bad_alloc)
{ return ::operator new(size); }
…
}
34. Item 52: 위치지정 new / delete
• 실제 사용 예
class Widget : public StandardNewDeleteForms { //인터페이스 상속
public:
using StandardNewDeleteForms::operator new; //이름영역 공유
using StandardNewDeleteForms::operator delete;
//별도의 위치지정 operator new /delete
static void* operator new(size_t size, void* pAlloc) throw (bad_alloc);
static void operator delete(void* pMemory, void* pAlloc) throw ();
}