Upload
-
View
159
Download
2
Embed Size (px)
Citation preview
Item 49: new 처리자
• new 처리자• 메모리 할당이 제대로 이루어지지 않은 경우 예외처리 !
• 예외처리에 앞서 사용자의 커스텀 예외처리자 , new-handler 가 호출됨
• set_new_handler 를 통해서 설정가능 .
namespace std {
typedef void (*new_handler) ();
new_handler set_new_handler(new_handler p) throw ();
}
Item 49: new 처리자
• new 처리자에서 해줄 수 있는 일들• 메모리 추가 확보
• 다른 new 처리자를 찾아서 호출
• NULL – 즉 default 예외처리
• 커스텀 예외처리 ( bad_alloc )
Item 49: new 처리자
• 클래스 별로 별도의 new handler 를 사용하고 싶다면• c++ 자체기능은 없으니 직접 만들어야함 .
• 클래스 커스텀 set_new_handler
• new 처리자 ( 함수 ) 를 받아오는 역할
• operator new 오버라이딩• 메모리 할당이 실패한 경우 클래스 자체 new-handler 를 호출
Item 49: new 처리자
• set_new_handler
• new_handler Widget::set_new_handler(new_handler p) throw()
{
new_handler oldHandler = currentHandler;
currentHanlder = p;
return oldHandler;
}
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 를 만들자 .
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;
}
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 복원
}
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);
}
Item 49: new 처리자
• 상속 받아서 사용하는 방법• class Widget : public NewHandlerSupport<Widget>
• template 에서 T 가 한번도 안쓰이는 데 ?
• 클래스별로 별도의 NewHandlerSupport 가 필요함• 클래스마다 각각 currentHandler 가 다를 것이므로 .
• T 를 받아서 별도의 NewHandlerSupport 를 만들어주기 위함
• CRTP 라고 하는 자주 사용되는 패턴
Item 49: new 처리자
• 예외 불가 new ( 추가 이슈 )
• 예전에 할당실패 시 bad_alloc Throw 말고 그냥 NULL 을 리턴했다 .
• 지금도 예외불가 new 하는 방법은 존재new (std::nothrow) Widget ;
• 하지만 예외불가는 오직 Widget 공간 할당에 한해서만 적용된다 .
• 내부 생성자에서 다시 new 를 해서 실패한 예외는 적용 안됨
• 어쨌든 new_handler 의 동작 구조를 잘 알아두자 .
Item 50: 적절한 operator new/delete
• 왜 new 와 delete 를 오버라이딩 하는가• 잘못된 힙 사용 방지
• delete 두 번
• 할당 영역을 넘어선 접근 ( 오버런 , 언더런 )
• 통계 수집• 할당된 메모리 블록의 크기 / 사용기간 분포
• 메모리 사용패턴
Item 50: 적절한 operator new/delete
• 왜 new 와 delete 를 오버라이딩 하는가• 할당 / 해제 성능 효율
• 특정한 조건에 할당 / 해제 작업에 최적화 할 수 있다 .
• 고정크기의 객체만 만드는 경우
• 싱글 쓰레드 환경이 보장된 경우
• 공간 오버헤드를 최소화• 안정적인 사용을 위해 다소 불필요한 메모리 공간 ( 관리 영역 ) 발생
• 별도로 관리자가 따로 있다면 관리영역을 많이 줄일 수 있다 .
Item 50: 적절한 operator new/delete
• 왜 new 와 delete 를 오버라이딩 하는가• 바이트 정렬 보장
• 시스템 아키텍처를 최대한 활용하기 위해 메모리를 8 바이트 단위 정렬할 수 있다 .
• 기존 컴파일러가 이를 보장하지 않는 경우가 종종 있기 때문에 별도로 제작
• 같은 자료구조 , 관계 있는 객체들의 물리적 locality 증대• 객체를 별도로 할당을 관리하여 메모리의 locality 를 증대시킬 수 있다 .
• new / delete 시 별도의 동작 추가
Item 50: 적절한 operator new/delete
• 오버런 / 언더런 처리 operator new
void* operator new (size_t size) throw (bad_alloc)
{
size_t realSize = size + 2 * sizeof(int); // 경계 표시영역 2 개void* pMem = malloc(realSize);
if(!pMem) throw bad_alloc();
*(static_cast<int*>(pMem)) = 0xDEADBEEF; // 경계표시 1 ( 언더런 방지 )
*(reinterpret_cast<int*>(static_cast<BYTE*> // 경계표시 2 ( 오버런 방지 )
(pMem) + realSize – sizeof(int))) = 0xDEADBEEF;
return static_cast<BYTE*>(pMem) + sizeof(int); // 실제 메모리 시작위치 반환
}
Item 50: 적절한 operator new/delete
• 위 operator new 의 문제점• new_handler 호출 루프가 없음 (item 51 에서 자세히 )
• 바이트 정렬• malloc 해서 받은 값에서 int 만큼 offset 이동시킨 주소를 리턴 하고 있음
• 메모리가 제대로 정렬된 상태가 아닐 가능성 !
• 2^n 크기로 데이터들을 정렬해야 최적화된다 .
• 아키텍처에 따라서 잘못된 동작을 야기할 수 도 있다 .
• 멀티 쓰레드에 대한 개념 전혀 없음
Item 50: 적절한 operator new/delete
• 가능하면 전문가에게 맡기자• 메모리 관리자를 잘 만들기란 쉽지 않은 일이다 .
• 일반적인 경우 컴파일러의 기본 operator new/delete 는 훌륭하다 .
• 특별한 경우 라이브러리의 도움을 받자 .
• boost::Pool : 크기가 작은 객체를 많이 할당 / 해제하는 경우
• 정렬 이슈 / 멀티 쓰레드 이슈가 잘 정리되어 있다 .
• 직접 만들기 전에 라이브러리 코드를 보고 배우는 것도 도움이 될 것이다 .
Item 51: operator new/delete 의 convention
• operator new 에서 convention
• 확실한 반환 값• 요청된 메모리의 포인터를 반환
• 0 size 메모리 요청 대비
• 할당 실패시 우선 new 처리자 함수를 호출
• 그래도 안되면 bad_alloc throw
• 기존 new 를 완전히 덮어 쓰지 말것 (item 52 에서 자세히 )
Item 51: operator new/delete 의 convention
• 실제로 구현 하기void* operator new (size_t size) throw (bad_alloc)
{
if( size == 0 ) size = 1; //0 바이트 요청은 1 바이트로 바꿔서while( true ) { // 할당이 되던가 , bad_alloc 이 뜨던가 둘중하나가 될때까지 반복void* pMem = malloc( size );
if( pMem != NULL) return static_cast<T>(pMem);
// 할당 실패시 현재 new_handler 찾아서 실행
new_handler globalHandler = set_new_handler( NULL );
set_new_handler(globalHandler);
if( globalHandler ) (*globalHandler)( );
else throw bad_alloc( ); //new_handler 없으면 bad_alloc 처리
}
}
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 바이트 체크까지 되는 코드임
Item 51: operator new/delete 의 convention
• operator new[] 에서 convention
• 단순히 주어진 사이즈의 메모리 덩어리를 할당하면 된다 .
• 배열 정보를 함부로 추론하려 하지마라• 몇 개의 객체가 들어올지 예측할 수 없다 .
• new 를 호출할 파생 클래스 객체의 사이즈를 알 수 없다 .
• 안다고 해도 new [] 는 실제 객체 * 배열 길이 보다 더 큰 사이즈를 할당한다 .
• item 16 에서 언급한 헤더정보가 추가된다 .
Item 51: operator new/delete 의 convention
• operator delete 에서 convention
• null 포인터 exception 만 조심하면 된다 .
void operator delete( void* rawMemory ) throw ()
{
if ( rawMemory == NULL ) return; //NULL delete 방지// 메모리 해제 작업…
}
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;
}
// 메모리 해제 작업…}
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 인가 ?)
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 의 미사용 공간에 원소 객체를 할당 - 생성 할 때
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 라고 부른다 .
Item 52: 위치지정 new / delete
• 만약 예외처리 없이 일반적으로 delete 를 호출한다면 ?
• 런타임 시스템은 기본형의 operator delete 를 호출한다 .
• 위치지정 delete 는 위치지정 new 의 Pair 가 필요할 때만 호출된다 .
• 따라서 표준형태의 operator delete 는 기본으로 항상 마련해야 한다 .
• 추가적으로 위치지정 new 를 썼다면 위치지정 delete 도 만들어야 한다 .
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
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); }
…
}
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 ();
}