effective c++chapter8

32
Effective C++ Chapter 8 정정

Upload: -

Post on 13-Aug-2015

25 views

Category:

Technology


1 download

TRANSCRIPT

Effective C++ Chapter 8 정리

new 와 delete 를 내 맘대로

들어가기에 앞서

- 가비지 컬렉터가 없는 C++, 불편하지만 이점도 있다

- 메모리를 수동으로 관리하고 싶다 ! 면 C++!

- Operator new, delete 문제 , 다중쓰레드에서의 메모리 관리 문제등등 !

- 이번 챕터를 통해서 해결해보자

- STL 컨테이너와 관련된 메모리 관리도 !

Item 49

new 처리자 (new handler) 의 동작 원리를 제대로 이해하자

Item 49 : new 처리자의 동작원리를 제대로 이해하자

- operator new 를 통해 메모리를 획득하지 못한 경우 ( 즉 , 할당할 메모리가 없을 때 )

- operator new 는 예외를 던진다

- 메모리 할당이 제대로 되지 못한 경우 , 예외를 던지기 전에 에러 처리 함수를 먼저 호출하게 되어있다이 에러 처리 함수를 new 처리자 ( 할당 에러 처리자 ) 라 한다

- 표준에는 이 new 처리자를 사용자가 지정해 줄 수 있는 함수가 존재한다

- 바로 set_new_handler

Item 49 : new 처리자의 동작원리를 제대로 이해하자

- set_new_handler 의 사용 방법

void outOfMem (){ std : : cerr << "Unable to sat isfy r equest for memory\n "; std : : abort ( ) ;}----------------------------------------------------------------------------------------------------- int main (){ std : : set_ new_ handler (outOfMem) ; // 사용자 정의 new handler 설정 int *pBigDataArray = new int [1 00000000L] ;}

1 억개의 정수가 할당되지 못하면 ? outOfMem 이 호출된다 . 그리고 std::abort에 의해프로그램이 종료된다 .

Item 49 : new 처리자의 동작원리를 제대로 이해하자

- new 처리자 함수의 지향점

1. 사용할 수 있는 메모리를 더 많이 확보하는 쪽

2. 다른 new 처리자를 부르는 쪽 -> 현재의 new 처리자로는 메모리를 할당할 수 없으므로

3. new 처리자를 제거하는 쪽 -> 바로 예외를 던짐

4. 제거고 뭐고 그냥 예외를 던지는 쪽

5. 프로그램을 끝내는 쪽

- 위 5 개 중 한 쪽을 택해 new 처리자를 구현해야 한다 .

Item 49 : new 처리자의 동작원리를 제대로 이해하자

- 클래스 별로 다른 new 처리자를 만들고 싶다면 ? ( Widget 의 예를 통해 알아 보자 )

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; // static 이니 구현은 cpp 에서 해준다}

static 을 사용하는 이유는 new 를 실행하다 실패하면 new_handler 를 부르기 때문에Widget 생성되기 전에 존재해야 한다 .

Item 49 : new 처리자의 동작원리를 제대로 이해하자

- 클래스 별로 다른 new 처리자를 만들고 싶다면 ? ( Widget 의 예를 통해 알아 보자 )

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; // static 이니 구현은 cpp 에서 해준다}

static 을 사용하는 이유는 new 를 실행하다 실패하면 new_handler 를 부르기 때문에Widget 생성되기 전에 존재해야 한다 .

Item 49 : new 처리자의 동작원리를 제대로 이해하자

- Widget::set_new_handler 구현 부분

std : :new_handler Widget : : set_new_handler(std : :new_handler p) throw() { std : :new_handler oldHandler = currentHandler ; currentHandler = p ;

// 원래 표준 set_new_handler 처럼 인자를 currentHandler 로 설정하고 // old 를 반환한다 . return oldHandler ; }

Item 49 : new 처리자의 동작원리를 제대로 이해하자

Widget 만의 operator new 구현하기 위해 new Handler 관리 클래스 선언

class NewHandlerHolder {public : explicit NewHandlerHolder(std : :new_handler nh) : handler(nh) {} ~NewHandlerHolder() { std: :set_new_handler(handler) ; } // 소멸자에서 하는 작업 뒤에서 설명

private : std::new_handler handler; NewHandlerHolder(const NewHandlerHolder&) ; // 복사 막기위해 NewHandlerHolder& operator=(const NewHandlerHolder&);}

Item 49 : new 처리자의 동작원리를 제대로 이해하자

Widget 만의 operator new 구현

void * Widget : :operator new(std: : size_t size) throw(std : :bad_alloc) {

// set_new_handle 의 반환 값은 기존의 new 처리자이다 . // 따라서 NewHandlerHolder::handler 는 기존의 new 처리자를 갖는다 NewHandlerHolder h(std: : set_new_handler(currentHandler)) ;

// 표준 할당자로 할당함 return : :operator new(size); }// NewHandlerHolder 의 소멸자에서 기존의 new 처리자로 되돌리는 것을 알 수 있다 .

이후에는 템플릿으로 만드는 법이 나온다 . 이는 다음에 자세히 살피고 이번 Item 을 넘어가자

Item 50

new, delete 는 언제 바꿀까 ?

Item 50 : custom operator new, delete 를 사용하는 경우

1. 잘못된 힙 사용을 탐지하기 위해

- operator new 에서 할당된 메모리 주소의 목록을 유지

- operator delete 에서 그 목록으로부터 하나씩 제거

- 한 번 new 한 객체를 두 번 delete 하는 경우가 없게 !

- overrun( 할당된 메모리 블록 끝을 넘어 기록하는 것 )

- underrun( 할당된 메모리 블록을 앞서 기록하는 것 )

- over,under run 문제 방지

Item 50 : custom operator new, delete 를 사용하는 경우

2. 효율 향상을 위해

- 컴파일러가 기본으로 제공하는 new, delete 는 다양한 요구사항을 맞추기 위해 무난하게 동작함

- 개발자가 자신에 맞는 동적 메모리 사용 방식을 정확히 안다면 ` 우수한 성능 ` 의 new, delete 만들기 가능

- 실행속도가 빠르고 ( 최대 10 의 n 제곱배 ) 메모리도 적게 차지하는 (50% 만 사용 ) 버전 만들기 가능

Item 50 : custom operator new, delete 를 사용하는 경우

3. 동적 할당 메모리의 실제 사용에 관한 통계 정보를 수집하기 위해

- 할당되는 메모리 블록의 크기 값들에 대한 정보

- 메모리 할당 순서가 FIFO, LIFO 인지

- 각 실행단계에서의 메모리 블록 할당이 어떻게 이루어지고 있는지

- 등등의 정보를 모으기 위해

Item 50 : custom operator new, delete 를 사용하는 경우

3. 동적 할당 메모리의 실제 사용에 관한 통계 정보를 수집하기 위해

- 할당되는 메모리 블록의 크기 값들에 대한 정보

- 메모리 할당 순서가 FIFO, LIFO 인지

- 각 실행단계에서의 메모리 블록 할당이 어떻게 이루어지고 있는지

- 등등의 정보를 모으기 위해

Item 50 : custom operator new, delete 를 사용하는 경우

여기서 잠깐 ! operator new 를 만들 때 주의할 점 : 바이트 정렬 (alignment)

- 컴퓨터는 대부분의 경우 특정 종류의 메모리 주소를 시작 주소로 하여 저장될 것을 요구한다

- 무슨 말이고 ? 만약 4 의 배수를 기준으로 각 객체가 저장되어야 한다면 객체들의 시작 주소는 언제나 0, 4, 8 ,c 로 끝난다 .

- 컴퓨터에 따라서 8byte 일 수도 , 4byte 일 수도 , 조금 다른 수 일 수 도 있다 .

- 어떤 아키텍처이냐에 따라 바이트 정렬이 안되면 프로그램이 실행되다가 하드웨어 예외를 일으킬 수 있다

- x86 은 어떤 바이트 단위에 맞추더라도 실행할 수 있지만 8 바이트 단위로 정렬하면 런타임 접근속도가 훨씬 빨라진다

Item 50 : custom operator new, delete 를 사용하는 경우

4. 할당 및 해제 속력을 높이기 위해

- 기본 제공 new 는 느린 경우도 많다

- 특정 크기를 할당할 일이 많으면 직접 만들어 속도에서 향상을 만들어 낼 수 있다

Item 50 : custom operator new, delete 를 사용하는 경우

5. 임의의 관계를 맺고 있는 객체들을 한 군데에 나란히 모아놓기 위해

- 특정 자료 구조 몇 개가 동시에 쓰이고 이들에 대해서 page fault 발생 횟수를 최소로 하고 싶을 때

- 해당 자료구조를 담은 힙을 할당해 준다 .

6. 원하는 작업을 수행하도록 하기 위해

- operator new, delete 에서 해주고 싶은 작업이 있으면 자기가 새로 만들어야 한다

Item 51

new 및 delete 를 작성할 때 따라야 할

기존의 관례를 잘 알아두자

Item 51 : new 및 delete 관련 관례들

Item 50 을 통해 언제 new, delete 를 새로 만들어야 할지 알았다 .

이번에는 어떻게 만들까의 문제

먼저 operator new 의 경우

반환 값 문제

- 요청한 메모리를 마련해 줄 수 있다면 그 메모리에 대한 포인터를 반환

- 마련해 줄 수 없다면 Item 49 를 다시 보자

Item 51 : new 및 delete 관련 관례들

- 반환 값을 어떻게 해줄것인가의 문제 말은 쉽다 . 구현은 ?void * operator new(std : : size_ t size) throw(std : :bad_alloc){ using namespace std ; if (size == 0) {size = 1; } // size 0 일 때도 적법한 주소를 반환해야 한다는 정책 때문에

while (true) { // 무한루프 가능성 있음 , new 처리자는 어떤 식으로든 마침표를 찍어야함 if( 할당이 성공했음 ) { return ( 할당된 메모리에 대한 포인터 ) ; }

// 할당 실패시 new_handler globalHandler = set_new_handler(O) ; // 원래 new 처리자 획득 set_new_handler(global Handler ) ; If (globalHandler) (*globalHandler) () ; // new 처리자에서 뭔가 해주길 바람 else throw std : : bad_alloc() ; // Item 49 의 지향점 부분 참조 }}

Item 51 : new 및 delete 관련 관례들

operator new 는 상속 대상

만약 Base 와 Derived 의 크기가 다르면 ?

operator new 는 Base 의 크기만 할당해줄 뿐이다 .

그럴 때는

if(size != sizeof(Base)) // size 는 operator new 로 전달받은 Derived 클래스의 크기

return ::operator new(size); // 크기가 다르면 기본 new 사용

Item 51 : new 및 delete 관련 관례들

- operator new[] 는 웬만하면 하지 말자 .

- operator new[] 는 상속되어 사용될 수 있다 .

- 이 때 operator new[] 의 할당 크기를 정할 수 없다 .

- 왜냐하면 상속된 클래스의 경우 Base 와 크기가 다를수 있기 때문이다 .

- 요구되는 바이트 수 / sizeof(Base) 해도 만들기 원하는 객체의 개수가 몇 개인지 알수 없다

- 그리고 실제로 operator new[] 로 전달된 size 보다 더 큰 size 가 실제 사이즈일 수 있다 .

- 배열에서는 앞 뒤로 , 필요한 정보를 더 저장해야 하는 경우가 있기 때문이다 .

Item 52

위치지정 new 를 작성한다면 위치지정 delete 도 같이 준비하자

Item 52 : 위치지정 new 와 delete 의 짝을 맞추자

- 위치지정 new 가 뭐죠 ?

- operator new(std::size_t, + a 매개변수 );

- size 뿐만 아니라 +a 의 매개변수를 갖는게 위치지정 new 이다 .

- 왜 이름이 위치 지정인가 ?

- 보통 operator new(std::size_t, void* memory); 방식으로 많이 쓰였기 때문에

- void* memory 로 선언된 곳에 전달된 인자로 메모리 할당을 시작하는 위치를 지정해줬다

Item 52 : 위치지정 new 와 delete 의 짝을 맞추자

- 우리가 new 를 사용하면 두 가지 함수가 호출된다

- 하나는 메모리 할당 함수이고 하나는 해당 타입의 생성자이다

- 메모리를 할당한 상태인데 생성자에서 오류를 던질 수 있다

- 그러면 런타임 때 메모리를 해제해 줘야한다

- 그런데 이 new 가 위치지정 new 였다면 ?

- 런타임 시스템은 위치지정 new 와 똑같은 추가 매개변수 개수와 타입을 갖는 위치지정 delete 를 찾는다

Item 52 : 위치지정 new 와 delete 의 짝을 맞추자

- 그런데 같은 추가 매개변수를 갖는 위치지정 delete 가 없다면 ?

- 여기서 추가라는 말을 쓰는 이유를 다음의 예에서 설명한다

- operator new(std::size_t, std::osstream& logStream)과operator delete(void* pMemory, std::osstream& logStream) 는같은 추가 매개변수를 갖는다 . 즉 std::size_t 와 void* pMemory 는 new, delete 각각에 기본인 것이다 .

- 다시 . 추가 매개변수가 짝이 안맞는다면 ? 아무 일도 안 한다 . 즉 메모리는 유실된 상태다 .

Item 52 : 위치지정 new 와 delete 의 짝을 맞추자

- 위치지정 delete 는 언제 호출 되는가 ?

- 기본적으로는 호출되지 않는다

- 위치지정 new 에서 오류가 던져졌을 때만 호출된다

- 즉 잘 처리된 객체를 지울 때는 표준 delete 가 사용되고 위치지정 new 를 사용하는 시점에오류가 발생하면 위치지정 delet 를 사용하게 된다

Item 52 : 위치지정 new 와 delete 의 짝을 맞추자

- C++ 전역 유효범위에서 제공하는 operator new 의 형태는 다음의 3 가지가 표준이다 .

- void* operator new(std::size_t) throw(std::bad_alloc); // 기본형

- void* operator new(std::size_t, void*) throw(); // 위치지정형

- void* operator new(std::size_t, const std::nothorw_t&) throw(); // 예외불가

- new 를 작성할 때 얘네들을 가리지 않도록 하는 것이 목표 !

Item 52 : 위치지정 new 와 delete 의 짝을 맞추자

- Class 하나에 기본형을 전부 넣어 둔다

- 해당 클래스를 상속 받는다

- using 등을 사용해 표준 형태가 내부에서 보이도록 한다

- 그리고 원하는 new 를 새롭게 정의한다 .

- 그러면 new 를 할 때 어떤 인자를 전달하느냐에 따라 표준 , 혹은 사용자 지정 new 를 사용할 수 있게 된다 .