Post List

2015년 1월 4일 일요일

new-handler의 동작 원리를 제대로 이해하자.

 new 함수로 메모리 할당에 실패한 경우 예외를 던지게 되어 있습니다. 이 에러 처리 함수를 가리켜 new 처리자(new-handler, 할당에러 처리자)라고 한다.

namespace std {
  typedef void (*new_handler)();
  new_handler set_new_handler(new_handler p) throw();
}

void outOfMem() {
  std::cerr << "Unable to satisfy request for memory\n";
  std::abort();
}

int main() {
  std::set_new_handler(outOfMem);
  int *pBifDataArrat = new int[100000000L];
  ...
}

 new-handler가 어떤 것인지 대충 알아볼수 있게 해주는 예시이다. 하지만 이것도 완벽하진 않다. new-handler애서 문자열을 또 할당해야하는 Code가 있는데 만약 저기서 또 에러가 발생한다면 무한루프에 빠질수도...

 new-handler는 다음 동작 중 하나를 꼭 해주어야 한다.

 - 사용할 수 있는 메모리를 더 많이 확보 : ex)시작시 메모리 블록을 크게 하나 할당해 놓았다가 여기서 그 메모리를 사용할 수 있게 허용하는 등...
 - 다른 new 처리자를 설치 : 현재 new-handler 안에서 set_new_handler를 호출
 - new-handler의 설치를 제거 : set_new_handler(NULL); 이러면 예외를 던지게 됨
 - 예외를 던짐 : bad_alloc 나 거기서 파생된 예외를 던짐
 - 복귀하지 않음 : abort 또는 exit 호출

 특정 class만을 위한 new-handler를 두고 싶다면, 직접 구현해야 한다. class 내에 new-handler 타입의 정적 멤버 데이터를 선언해서 거기에 보관해두고 필요할 때 set_new_hander로 선언을 해두며, 처리가 끝난 뒤 원래 new-handler로 다시 돌려주면 된다. new 구현에는 new-handler이 자원관리를 위한 RAII class를 이용하였다.

class NewHandlerHolder {
public:
  explicit NewHandlerHolder(std::new_handler nh) : handler(nh) {}
  ~NewHandlerHolder() { std::set_new_handler(handler); }
private:
  std::new_handler handler;

  NewHandlerHolder(const NewHandler&);
  NewHandlerHolder& operator= (const NewHandlerHolder&);
};

class Widget {
public:
  static std::new_handler set_new_handler(std::new_handler p) throw() {
    std::new_hander oldHandler = currentHandler;
    currentHandler = p;
    return oldHandler;
  }
  static void* operator new(std::size_t size) throw(std::bad_alloc) {
    NewHandlerHolder h(std::set_new_handler(currentHandler));
    return ::operator new(size);
  }
private:
  static std::new_handler currentHandler; // 클래스 구현 파일에서 NULL로 초기화 필요
};

 잠시만 생각해 보면, 이런 Code는 어떤 클래스를 쓰더라도 똑같이 나올 것 같다. 이런 용도에 손쉽게 쓸 수 있는 손질 방법으로는 믹스인(mixin) 양식의 기본 클래스를 추천하고 싶다. 템플릿으로 구현하여 각 클래스 별로 만들어지긴한데 그 안 어디에도 템플릿타입에 대한 Code는 없다. 이런 형식을 신기하게 반복되는 템플릿 패턴(curiously recurring template pattern: CRTP)라고 부른다.

template<typename T>
class NewHandlerSupport {
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;
};

template<typename T>
std::new_handler NewHandlerSupport<T>::set_new_handler(std::new_handler p) throw() {
  std::new_handler old = currentHandler;
  currentHandler = p;
  return old;
}

template<typename T>
void* NewHandlerSupprot<T>::operator new(std::size_t size) throw(std::bad_alloc) {
  NewHandlerHolder h(std::new_handler(currentHandler));
  return ::operator new(size);
}

template<typename T>
std::new_handler NewHandlerSupport<T>::currentHandler = 0;

class Widget : public NewHandlerSupport<Widget> { ... };

 * set_new_handler 함수를 쓰면 메모리 할당 요청이 만족되지 못핼을 때 호출되는 함수를 지정할 수 있습니다.




 * 예외불가(nothrow) new는 영향력이 제한되어 있습니다. 메모리 할당 자체에만 적용되기 때문입니다. 이후에 호출되는 생성자에서는 얼마든지 예외를 던질 수 있습니다.

댓글 없음:

댓글 쓰기