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

항목 51. new 및 delete를 작성할 때 따라야 할 기존의 관례를 잘 알아두자

by ocean20 2020. 5. 12.

operator new, delete 사용자 정의버전의 요구사항을 살펴보자.

 

1. operator new 요구사항

  1) 반환값

  - 메모리 할당 가능? 해당 메모리에 대한 포인터 반환 : bad_alloc 타입의 예외;

  2) 가용메모리 부족?

  -  메모리 부족시 new처리자 함수호출하여 2회이상 메모리할당 재시도한다.

  -  operator new 예외를 던지는 경우 : new처리자 함수에 대한 포인터가 null일때만

  3) 크기가 없는 메모리 요청에 대한 대비책 필요

  - 0바이트 메모리 요청시 1바이트 요구로 간주하여 처리한다.

  - (0바이트 요구는 거의없다고 보면된다..)

  4) "기본(normal)" 형태의 new 가려지지 않도록

  - 이건 차후 설명하겠다.

 

void * operator new(std::size_t size) throw (std::bad_alloc)
{
  using namespace std;
  
  if(size == 0) {  // 3번항목
    size = 1;
  }
  
  while(true) {
    size 바이트를 할당해봅니다;
    if(할당 성공)
      return (할당된 메모리에 대한 포인터) ;
    
    //2번항목 -> 할당실패시, 현재의 new처리자 함수가
    //어느 것으로 설정되어 있는지 찾아낸다.(아래를보세요)
    new_handler globalHandler = set_new_handler(0); //set_new_handler는 이전 new 핸드러를 반환
    set_new_handler(globalHandler);
    
    if(globalHandler) (*globalHandler)();
    else throw std::bad_alloc();  // 1번항목
    }
 }

*풀이

  - while(true) : 이 루프를 빠져나오는 조건은, (1) 메모리 할당이 성공하든지, (2) new처리자 함수쪽에서 해결해주든지 이다. (항목 49.참조) 

  - operator new 멤버 함수는 상속이 되는 함수이다.  위 함수의 매개변수는 size이다. 즉 이 멤버변수는 해당 특정 클래스 '만'을 위한 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가 호출!

*풀이

  - 만약 Base클래스의 operator new가 이런 상황에 대해 조치를 취하지 않았다면 문제가 생긴다. 전체 설계를 바꾸지 않고 operator new만 바꿔서 해결하는 방법을 살펴보자. 

 

static void * operator new (std::size_t size) throw(std::bad_alloc)
{
  if (size != sizeof(Base))  // "틀린" 크기가 들어오면, 표준 operator new 쪽에서 처리하도록 넘긴다.
    return :: operator new(size);
  ...  // "맞는" 크기가 들어오면 여기서 처리한다.
}

*풀이

  - 위 "1. - 4) 기본형태의 new가 가려지지 않도록한다." 를 보여주는 의사코드이다.

 

2. operator delete

class Base {  // 이전과 같으나, 지금은 operator delete가 추가된 상태
public :
  static void * operator new(std::size_t size) thro(std::bad_alloc);
  static void operator delete(void *rawMemory, std::size_t size) throw();
  ...
};

void Base::operator delete(void *rawMemory, std::siz_t size) throw()
{
  if(rawMemory == 0) return;  // 널 포인터에 대한 점검
  
  if(size != sizeof(Base)) { // 크기가 "틀린" 경우, 표준 operator delete가 처리하도록 넘긴다.
  ::operator delete(rawMemory);
  return;
  }
  rawMemory가 가리키는 메모리를 해제한다.
  
  return;
}

*풀이

  - operator new와 거의 동일하나, 해제 포인터가 null 일경우 아무것도 하지 않고 return한다.

  - 그리고 Base 사이즈와 다를 경우, 표준 operator delete가 처리하도록 권한을 넘긴다.

 

 

이것만은 잊지 말자!

  - 관례적으로, operator new 함수는 메모리 할당을 반복해서 시도하는 무한 루프를 가져야 하고, 메모리 할당 요구를 만족시킬 없을때 new 처리자를 호출해야하며, 0바이트에 대한 대책도 필요하다. 클래스 전용 버전은 자신이 할당하기로 예정된 크기보다 (틀린)메모리 블록에 대한 요구도 처리해야 한다.

  - operator delete함수는 포인터가 들어왔을 아무일도 하지 않아야 한다. 클래스전용버전의 경우에는 예정 크기보다 블록을 처리해야한다.

댓글