본문 바로가기
책 정리/Effective C++ 3rd

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

by ocean20 2020. 5. 12.
Widget *pw = new Widget; 

함수 두개가 호출된다.
1. operator new
2. Widget 생성자

만약 1번 operator new에서 메모리 할당후 2번 생성자에서 에러 발생시
2번 생성자에서 메모리 해제역할을 할 수 없다.
이 메모리 해제 역할을 C++ 런타임 시스템에서 맡는다.

런타임 시스템의 역할은 1번에서 호출한 operator new와 짝이되는 버전의 operator delete를 호출하는것이다.

여태까지 우리가 상대했던 기본형 new. delete를 대수로운 사안이 아니다.

void operator delete(void *rawMemory) throw(); // 전역 유효범위의 기본형 시그너처 
void operator delete(void *rawMemory, std::size_t size) throw(); // 클래스 범위의 기본형 시그너처 

이와 같은 기본형 new에 대해서는 런타임시스템이 잘 추적하여 메모리를 해제한다.

 

하지만, 비표준 형태의 operator new에서 메모리할당후 생성자에서 에러를 뱉어낸다면 어떻게 될까 ?

아래 코드를 살펴보자.

class Widget{
public :
  ...
  static void* operator new(std::size_t size, std::ostream& logStream)
  throw(std::bad_alloc);

  static void operator delete(void *pMemory, size_t size) throw();
 ...
};

*풀이

  - operator new(std::size_t size, std::ostream& logStream)과 매칭되는 operator delete가 없기 때문에, 생성자에서 에러발생시 런타임시스템은 위 new와 매칭되는 delete를 찾아내지 못하여 아무것도 호출을 못한채 종료된다.

 

좀더 자세히 알아보기 위해 용어 정리부터 짚고 넘어가자


1. 위치지정 new

  1) operator new 함수는 기본형과 달리 매개변수를 추가로 받는 형태로도 선언가능하다. 이를 위치치정(placement) new라고 한다.

  2) 이 new는 여러가지 매개변수가 올 수 있겠지만, 특히 유용한놈이 있다. 그건 바로, 어떤 객체를 생성시킬 메모리 위치를 나타내는 포인터를 매개변수로 받는 것이 그 주인공이다.

    - 예시코드 : void * operator new(std::size_t , void *pMemory) throw(); // 위치지정 new

 

2. 위치지정 new의 동작 메커니즘

  1) 런타임시스템은 operator new와 매칭되는 operator delete를 추적한다고 했다. 즉 매개변수의 개수와 타입이 일치하는 놈을 골라낸다. 

  2) 위치지정 delete가 호출되는 경우는 위치치정 new의 호출에 '묻어서' 함께 호출되는 생성자에서 예외를 뱉어낼 경우이다.


3. 이름 가리기를 유의하자!

  - 클래스 전용 new가 다른 유효범위의 new들을 가릴 수 있다.

class Base{
public:
  ...
  static void* operator new(std::size_t size, std::ostream& logStream) // 이 new가 표준형태 new를 가림
  throw(std::vad_alloc);
  ...
};

Base *pb = new Base; // Error! 표준 형태의 전역 new가 가려진다.

Base *pb = new (std::cerr) Base // Base의 위치지정 new를 호출한다.

 

  - C++가 제공하는 전역 유효범위의 operator new의 형태 3가지 표준을 기억해두자.

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

 

  - C++ 3가지 표준에 대한 예시코드를 살펴보자.

class StandardNewDeleteForms {  
public : 
  // 기본형 new / delete 
  static void* operator new(std::size_t size) throw(std::bad_alloc) 
  { return ::operator new(size); } 
   
  static void operator delete(void *pMemory) throw() 
  { ::operator delete(pMemory); } 

  //위치지정 new / delete 
  static void* operator new(std::size_t size, void *ptr) throw() 
  { return ::operator new(size, ptr); } 

  static void operator delete(void *pMemory, void *ptr) throw() 
  { ::operator delete(pMemory, ptr); } 

  //예외불가 new / delete 
  static void* operator new(std::size_t size, const std::nothrow_t& nt) throw() 
  { return ::operator new(size, nt); } 

  static void operator delete(void *pMemory, const std::nothrow_t& nt) throw() 
  { ::operator delete(pMemory); } 
}; 

 

  - 표준형태를 물려받아 사용자 정의 형태로 확장해나간 예시

class Widget: public StandardNewDeleteForms { // 표준 형태를 물려 받는다. 
public : 
  using StandardNewDeleteForms::operator new;  //표준형태가  
  using StandardNewDeleteForms::operator delete;  //보이도록 한다. 

  static void* operator new(std::size_t size, std::ostream& logStream)  // 사용자 정의 위치치정 new 
  throw(std::bad_alloc); 

  static void operator delete(void *pMemory, std::ostream& logStream)  // 앞의 것과 짝이되는 위치지정 delete 이다.오후 2:40 2020-05-12 
  throw(); 
  ... 
}; 



이것만은 잊지 말자!
  - operator new 함수의 위치지정(placement) 버전을 만들때는, 이 함수와 짝을 이루는 위치지정 버전의 operator delete함수도 만들자! 빠트릴 경우, 메모리 누출 현상을 경험하게 된다.
  - new 및 delete의 위치치정 버전을 선언할 때는, 이들의 표준버전이 가려지지 않도록 유의하자.

댓글