34
EFFECTIVE C++ 정정 Chapter 8 new / delete

Effective c++ 정리 chapter 8

  • Upload
    -

  • View
    159

  • Download
    2

Embed Size (px)

Citation preview

EFFECTIVE C++ 정리Chapter 8

new / delete

new 처리자ITEM 49

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 의 동작 구조를 잘 알아두자 .

적절한 operator new/delete

ITEM 50

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 : 크기가 작은 객체를 많이 할당 / 해제하는 경우

• 정렬 이슈 / 멀티 쓰레드 이슈가 잘 정리되어 있다 .

• 직접 만들기 전에 라이브러리 코드를 보고 배우는 것도 도움이 될 것이다 .

operator new/delete 의 convention

ITEM 51

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;

}

// 메모리 해제 작업…}

위치지정 new / deleteITEM 52

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 ();

}