[ Effective C++ ] 항목 49 : new 처리자의 동작 원리를 제대로 이해하자

Minsu._.Lighting·2023년 12월 14일
0

[ Effective C++ ] 정리 모음집
" C++ 프로그래머의 필독서, 스콧 마이어스의 Effective C++ 를 읽고 내용 요약 / 정리 "

[핵심]

" 보통의 new를 쓰든, 예외불가 new를 쓰든 new 처리자의 동작 원리를 제대로 이해하자는 말은 변함이 없다! "

  • set_new_handler 함수를 쓰면 메모리 할당 요청이 만족되지 못했을 때 호출되는 함수를 지정할 수 있다!
  • 예외불가 new는 메모리 할당 자체에만 적용되기 때문에 영향력이 제한되어 있다. 이후에 호출되는 생성자에서는 얼마든지 예외를 던질 수 있다!

💡 new 처리자(new-handler, 할당 에러 처리자)

  • 메모리 할당이 제대로 되지 못한 상황에 대한 반응으로 operator new 가 예외를 던지기 전 사용자 쪽에서 지정할 수 있는 에러 처리 함수를 우선적으로 호출하게 되있다. 그리고 이 에러를 가리켜 new 처리자 라고 한다

📌 set_new_handler

namespace std
{
	typedef void(*new_handler)();
    
    new_handler set_new_handler(new_handler p) throw();
}
  • 메모리 고갈 상황을 처리할 함수를 사용자 쪽에서 지정할 수 있도록 표준 라이브러리에서 준비한 함수
    - <new> 에 선언되어 있음

  • new_handler는 함수의 포인터에 대해 typedef를 걸어놓은 타입 동의어
    - new_handler는 받는 것도, 반환하지도 않는다

  • set_new_handler 가 받는 매개 변수는(new_handler 타입) 메모리를 제대로 할당하지 못한 operator new 가 호출 할 함수의 포인터, 반환값은 set_new_handler가 호출 되기 전 new 처리자로 쓰이던 함수의 포인터


📌 new 처리자 함수가 프로그램의 동작에 좋은 영향을 주기 위해서 해야하는 행동

  • 사용할 수 있는 메모리를 더 많이 확보한다!
    - operator new 가 시도하는 이후의 메모리 확보가 성공할 수 있도록 하자는 전략
    - 대표적인 방법으로는 프로그램이 시작할 때 메모리 블록을 크게 하나 할당해 놓았다가 new 처리자가 가장 처음 호출 될 때 그 메모리를 쓸 수 있도록 허용하는 방법이 있다
    (구현 방법은 이외에도 여러가지가 있다)

  • 다른 new 처리자를 설치한다
    - 현재의 new 처리자 안에서 set_new_handler를 호출

  • new 처리자의 설치를 제거한다
    - 즉, set_new_handler에 널 포인터를 넘긴다

  • 예외를 던진다
    - bad_alloc 혹은 bad_alloc에서 파생된 타입의 예외를 던진다

  • 복귀하지 않는다
    - abort() 혹은 exit()를 호출

📢 반드시 위 동작 중 하나를 꼭 해줘야 한다!


📌 할당된 객체의 클래스 타입에 따라 메모리 실패에 대한 처리를 다르게 가져가고 싶다면?

class X
{
	public:
    	static void outOfMemory();
        ...
};

class Y
{
	public:
    	static void outOfMemory();
        ...
};

X* p1 = new X;		// 메모리 할당이 실패했을 경우
					// X::outOfMemory를 호출
                    
Y* p2 = new Y;		// 메모리 할당이 실패했을 경우
					// Y::outOfMemory를 호출
  • 위 처럼 동작하게 만들고 싶다면, 해당 클래스에서 자체 버전의 set_new_handleroperator new를 제공하도록 만든다
    - C++에는 특정 클래스만을 위한 할당 에러 처리자를 둘 수 있는 기능 같은 것이 없다. 따라서 직접 구현한다

  • 자체 버전의 set_new_handler

class Widget
{
public:
	static new_handler set_new_handler(new_handler p) throw();
    static void* operator new(size_t size) throw(bad_alloc);
    
private:
	static new_handler currentHandler;
};

new_handler Widget::currentHandler = 0; // 클래스 구현 파일에서

new_handler Widget::set_new_handler(new_handler p) throw()
{
	new_hnadler oldHandler = currentHandler;
	currentHandler = p;
    return oldHandler;
}
  • 자체 버전의 operator new
class NewHandlerHolder
{
public:
	explicit NewHandlerHolder(new_handler nh)
    : handler(nh) {}
    
    ~NewHandlerHolder()
    { set_new_handler(handler); }
    
private:
	new_handler handler;
    
    NewHandlerHolder(const NewHandlerHolder&);
    NewHandlerHolder&
    	operator=(const NewHandlerHolder&);
 };
 
 void* Widget::operator new(size_t size) throw(bad_alloc)
 {
 	NewHandlerHolder
    	h(set_new_handler(currentHandler));
        
    return ::operator new(size);
 }
 
 void outOfMem();
 
 Widget::set_new_handler(outOfMem);
 
 Widget* pw1 = new Widget;
 
 string* ps = new string;
 
 Widget::set_new_handler(0);
 
 Widget* pw2 = new Widget;

📢 이 코드를 다른 클래스에서도 재사용 할 수 있도록 해보자!

template<typename T>
class NewHandlerSupport
{
public:

	static new_handler set_new_handler(new_handler p) throw ();
    static void* operator new(size_t size) throw(bad_alloc);
    
    ...
    
private:
	static new_handler currentHandler;
};

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

temaplate<typename T>
void* NewHandlerSupport<T>::operator new(size_t size) throw(bad_alloc)
{
	NewHandlerHolder h(set_new_handler(currentHandler));
    return ::operator new(size);
}

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

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

📌 예외 불가 new

class Widget { ... };

Widget* pw1 = new Widget;

if (pw1 == 0) ...

Widget* pw2 = new (nothrow) Widget;

if (pw2 == 0) ...
  • 원래 C++은 operator new를 이용한 메모리 할당 실패 시 널 포인터를 반환 하다가 bad_alloc 예외를 던지도록 명세가 바뀌었으나, C++ 표준화 위원회는 '널 포인터 점검' 기반의 코드를 버리고 싶지 않아 전통적인 '할당 실패 시 널 반환' 으로 동작하는 대안적인 형태의 operator new 를 같이 내놓음
    - 이런 형태를 예외불가 형태라고 함
profile
오코완~😤😤

0개의 댓글

관련 채용 정보