[Effective C++] 51. new 및 delete를 작성할 때 따라야 할 기존의 관례를 잘 알아 두자
operator new의 기본 요구사항
1. 반환 값이 제대로 되어있어야 한다.
- 요청된 메모리를 마련할 수 있는 경우 반환 값은 메모리에 대한 포인터이다.
- 요청된 메모리를 마련할 수 없는 경우 bad_alloc 타입의 예외를 던진다.
2. 가용 메모리가 부족할 경우에는 new 처리자 함수를 호출해야 한다.
3. 크기가 없는 메모리 요청(0바이트)에 대한 대비책을 갖춰두어야 한다.
4. 기본 형태의 new가 가려지지 않도록 한다.
비멤버 버전의 operator new 함수 의사 코드
operator new 멤버 함수는 파생 클래스 쪽으로 상속이 되는 함수이다.
// 다른 매개변수를 추가로 가질 수 있다. void * operator new(std::size_t) throw(std::bac_alloc) { using namespace std; if (size == 0) { size = 1; // 0 바이트 요청이 들어오면 1 바이트 요구로 간주하고 처리 } while(true) { size 바이트 할당 if (할당 성공) return (할당된 메모리에 대한 포인터) // 할당이 실패했을 경우 // 현재의 new 처리자 함수가 어느 것으로 설정되어 있는지 찾는다. new_handler globalHandler = set_new_handler(0); set_new_handler(globalHandler); if(globalHandler) (*globalHandler)(); else throw std::bac_alloc(); } }
보면 무한루프(while(true) 문)를 빠져나오는 방법으로는 메모리 할당이 성공하거나 아니면 항목 49에서 이야기한 동작들 중 한 가지를 new 처리자 함수 쪽에서 해주든지 둘 중 하나이다.
new 처리자에서 처리를 안 하면 operator new의 내부 루프는 스스로 끝나지 않는다.
operator new클래스 전용 버전
class Base { public: static void * operator new(std::size_t size) throw(std::bad_alloc); }; class Derived : public Base {...}; // Derived에서는 operator new가 선언되지 않는다. derived * p = new Derived; // Base::operator new가 호출된다. void * Base::operator new (std::size_t size) throw(std::bad_alloc) { if (size != sizeof(base)) { // 틀린 크기가 들어오면 표준 operator new 쪽에서 메모리 할당 요구를 처리한다. return ::operator new(size); } // 맞는 크기가 들어오면 메모리 할당 요구를 여기서 처리한다. }
C++ 에는 모든 독립 구조의 객체는 반드시 크기가 0이 넘어야 한다는 금기사항이 있기 때문에, sizeof(Base)와 size를 비교하는 코드에서 0바이트 점검도 함께 진행한다. size가 0이면 메모리 처리 요구가 ::operator new 쪽으로 넘어간다.
배열에 대한 메모리 할당을 클래스 전용 방식으로 하고 싶다면 operator new [] 함수를 구현하면 된다.
operator delete 작성 규칙
operator delete를 작성할 때의 관례는 C++는 널 포인터에 대한 delete 적용이 항상 안전하도록 보장한다.
아래의 비멤버 버전 operator delete의 의사 코드를 보자.
void operator delete(void *rawMemory) throw() { // 널 포인터가 delete되려고 할 경우에는 아무것도 하지 않는다. if(rawMemory == 0) return; // rawMemory가 가리키는 메모리 해제 }
operator delete 클래스 전용 버전
operator delete의 클래스 전용 버전도 단순하다. 삭제될 메모리의 크기를 점검하는 코드를 넣어주기만 하면 된다.
operator new 쪽으로 틀린 크기의 메모리 요청을 ::operator new 쪽으로 넘기도록 구현되었다고 가정하면, 클래스 전용의 operator delete 역시 틀린 크기로 할당된 메모리의 삭제 요청을 ::operator delete 쪽으로 전달하는 식으로 구현하면 된다.
class Base { public: 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; // 널 포인터 점검 // 크기가 틀린 경우 표준 operator delete가 메모리 삭제 요청을 맡는다. if (size != sizeof(Base)) { ::operator delete(rawMemory); return; } // rawMemory가 가리키는 메모리를 해제한다. return; }
가상 소멸자가 없는 기본 클래스로부터 파생된 클래스의 객체를 삭제하려고 할 경우에는 operator delete로 C++가 넘기는 size_t 값이 엉터리일 수 있다. 기본 클래스에서 가상 소멸자를 빼먹으면 operator delete 함수가 똑바로 동작하지 않을 수 있다.
요약
- 관례적으로, operator new 함수는 메모리 할당을 반복해서 시도하는 무한 루프를 가져야 하고, 메모리 할당 요구를 만족시킬 수 없을 때 new 처리자를 호출해야 하며, 0바이트에 대한 대책도 있어야 한다. 클래스 전용 버전은 자신이 할당하기로 예정된 크기보다 더 클(틀린) 메모리 블록에 대한 요구도 처리해야 한다.
- operator delete 함수는 널 포인터가 들어왔을 때 아무 일도 하지 않아야 한다. 클래스 전용 버전의 경우에는 예정 크기보다 더 큰 블록을 처리해야 한다.
'Books > Effective C++' 카테고리의 다른 글
[Effective C++] 53. 컴파일러 경고를 지나치지 말자 (0) | 2022.07.24 |
---|---|
[Effective C++] 52. 위치지정 new를 작성한다면 위치지정 delete도 같이 준비하자 (0) | 2022.07.24 |
[Effective C++] 50. new 및 delete를 언제 바꿔야 좋은 소리를 들을지 파악해 두자 (0) | 2022.07.16 |
[Effective C++] 49. new 처리자의 동작 원리를 제대로 이해하자 (0) | 2022.07.16 |
[Effective C++] 48. 템플릿 메타프로그래밍, 하지 않겠는가? (0) | 2022.07.16 |
댓글
이 글 공유하기
다른 글
-
[Effective C++] 53. 컴파일러 경고를 지나치지 말자
[Effective C++] 53. 컴파일러 경고를 지나치지 말자
2022.07.24우리는 우리가 사용하고 있는 컴파일러가 띄워주는 에러 메시지들에 어느 정도 익숙해지고 나면, 이 외의 다른 메시지들도 어떤 것은 넘어가도 되고, 어떤 것은 없애주어야 하는지 이해하게 되는 수준이 오게 된다. 우리는 경고 메시지의 참 뜻을 명확하게 이해해야 하며, 지워나가는 습관을 들어야 한다. 요약 컴파일러 경고를 쉽게 지나치지 말자. 컴파일러에서 지원하는 최고 경고 수준에도 경고 메시지를 내지 않고 컴파일되는 코드를 만드는 쪽에 전력을 다하자. 컴파일러 경고에 너무 기대는 인생을 지양하자. 컴파일러마다 트집을 잡고 경고를 내는 부분들이 천차만별이기 때문이다. 지금 코드를 다른 컴파일러로 이식하면서 여러분이 익숙해져 있는 경고 메시지가 온 데 간 데 없이 사라질 수 있다. -
[Effective C++] 52. 위치지정 new를 작성한다면 위치지정 delete도 같이 준비하자
[Effective C++] 52. 위치지정 new를 작성한다면 위치지정 delete도 같이 준비하자
2022.07.24new 표현식을 사용했을 때 호출되는 두 가지 함수 Widget * pw = new Widget; 위에서는 함수 두 개가 호출된다. 우선 메모리 할당을 위해 operator new가 호출되고, 그 뒤를 이어 Widget의 기본 생성자가 호출된다. 여기서 첫 번째 함수 호출은 무사히 지나갔는데 두 번째 함수 호출이 진행되다가 예외가 발생했다고 가정해보자. 이렇게 사고가 났을 경우 첫 단계에서 이미 끝난 메모리 할당을 취소해야 한다. 왜냐하면 메모리 누수가 발생하기 때문이다. 하지만 Widget 생성자에서 예외가 튀어나오면 pw에 포인터가 대입될 일은 절대로 안 생기기 때문에 사용자 코드에서는 이 메모리를 해제할 수 없다. 따라서 이 메모리의 해제는 C++ 런타임 시스템이 맡아줘야 한다. C++ 런타임 시스… -
[Effective C++] 50. new 및 delete를 언제 바꿔야 좋은 소리를 들을지 파악해 두자
[Effective C++] 50. new 및 delete를 언제 바꿔야 좋은 소리를 들을지 파악해 두자
2022.07.16일반적으로 컴파일러가 제공하는 operator new, operator delete는 대체로 쓸만하다. 그리고 사용자가 오버로딩을 통해 재정의한다면 수동으로 지정해줄 관례들이 있다. 따라서 기본 operator의 new와 delete를 사용해도 무관하지만 사용자가 본인의 프로젝트에 알맞은 오버로딩을 구현한다면 훨씬 효율적인 operator new , operator delete가 탄생할 수도 있는데 언제 operator new, delete를 바꾸어야 하는 지 알아보자. 사용자 정의 new와 delete를 고려해야 할 때 1. 잘못된 힙 사용을 탐지하기 위해 new 한 메모리에 delete 하는 것을 잊어버리면 메모리 누출이 되고, 이미 메모리 해제한 메모리를 또 메모리 해제한다면 프로그램은 터질 것이다… -
[Effective C++] 49. new 처리자의 동작 원리를 제대로 이해하자
[Effective C++] 49. new 처리자의 동작 원리를 제대로 이해하자
2022.07.16메모리 할당 과정 operator new는 메모리 할당이라는 임무를 맡고 있으며, 임무 실패 시 예외를 발생시킨다. 하지만, 예외를 던지기 전 사용자 에러 처리 함수를 선 호출하도록 설계되어있는데, 이 에러 처리 함수를 가리켜 new 처리자(new-handler, 할당 에러 처리자)라고 한다. 이와 같은 메모리 고갈 상황을 처리할 함수를 사용자 쪽에서 지정할 수 있도록 표준 라이브러리에서는 set_new_handler라는 함수가 준비되어있다. 이 함수는 에 선언되어 있다. namespace std { typedef void (*new_handler)(); new_handler set_new_handler(new_handler p) throw(); } new_handler는 받는 것도 없고 반환하는 것도 …
댓글을 사용할 수 없습니다.