일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 | 31 |
- rcsp
- 제네릭프로그래밍
- 부분복사
- sharedptr
- 암시적변환
- effectivec++.
- 가상기본클래스
- operator=
- RAII
- uniqueptr
- private상속
- 일반화복사생성자
- 게임프로그래밍
- 템플릿
- 해골책
- contextswitching
- 복사함수
- EffectiveC++
- 멤버함수템플릿
- Directx9
- 스택풀림
- 이른진단
- 상수객체참조
- c++
- most vexing parse
- 암시적인터페이스
- 교차dll문제
- fvf
- 도우미함수
- 정점버퍼
- Today
- Total
성공할 게임개발자
[Effective c++] [8. new와 delete를 내 맘대로] 49. new 처리자의 동작 원리를 제대로 이해하자 본문
new 처리자(new-handler 할당에러 처리자)
메모리 할당 요청을 operator new함수가 맞추어 주지 못할 경우 사용자 쪽에서 지정할 수 있는 에러 처리 함수를 우선적으로 호출하도록 하는 함수
namespace std
{
typedef void(*new_handler)();
new_handler set_new_handler(new_handler p) throw();
}
typedef void(*new_handler)() 의 의미 new_handler는 인자없고 void 반환형인 함수 포인터 타입의 별명
즉, void 반환, 인자 없음 형태의 함수 포인터 타입을 new_handler 라는 이름으로 정의한다의 뜻
위에서 set_new_handler는 new_handler를 받고 new_handler를 반환하는 함수.
set_new_handler가 받아들이는 new_handler 타입의 매개변수는 operator new가 할당하지 못했을 때 호출할 함수의 포인터이다
void outOfMem()
{
std::cerr << "Unable to satisfy request for memory";
std::abort();
}
int main()
{
std::set_new_handler(outOfMem);
int *bigDataArray = new int[100000000L];
...
}
new_handler 를 typedef로 정의했으므로(void 타입, 인자없음) set_new_handler에 인자로 넣을 수 있다.
이 함수는 메모리가 부족하여 할당 실패 시 에러 메세지를 출력하는데 만약 에러메세지를 출력하는데 메모리가 동적으로 할당 되어야 한다면? (예를 들면, 벌금을 내야하기위해 벌금을 내는 느낌?) 이 경우 operator new는 충분한 메모리를 찾아낼 때까지 new를 뒤풀이해서 호출한다. 이게 좋게 설계되어있을라면 아래 동작 중 하나를 꼭 해주어야 한다.
1. 사용할 수 잇는 메모리를 더 많이 확보한다
구현방법 중 하나는 프로그램이 시작할 때 메모리 블록을 크게 하나 할당해 놓았다가 new처리자가 가장 처음 호출될 때 그 메모리를 쓸 수 있게 하는 전략
2. 다른 new처리자를 설치
자기 몫을 해줄 다른 new 처리자를 설치한다.(new 처리자 안에서 set_new_handler 호출) operator new 가 다시 new 처리자를 호출할 때, 새로 설치된 new처리자가 호출되는 것. 혹은 new처리자가 자기 자신을 다른 방식을 동작하게 동작원리를 바꾼다.
3. new처리자의 설치를 제거
set_new_handler에 널 포인터를 넘김.
4.예외를 던짐
5. 복귀하지 않는다
대개 abort, exit 을 호출
C++에는 특정 클래스만 을 위한 할당 에러 처리자를 둘 수 잇ㄴ느 기능이 없다.
그냥 클래스에서 구현하면 된다 자체버전의 set_new_handler, operator new
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를 호출하여 실제 메모리 할당을 수행한다. 전역 operator new할당이 실패하면 이 함수는 Widget의 new처리자를 호출한다
자원 관리 클래스를 이용하여 RAII 연산
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&);
};
temaplate<typename T>
void* NewHandlerSupport<T>::operator new(size_t size) throw(bad_alloc)
{
NewHandlerHolder h(set_new_handler(currentHandler));
return ::operator new(size);
}
// widget의 operator new 구현
void* Widget::operator new(size_t size) throw(bad_alloc)
{
NewHandlerHolder h(set_new_handler(currentHandler));
return ::operator new(size);
}
// widget 객체에 대한 메모리할당 실패했을 때, 호출되는 함수
void outOfMem();
// new처리자 함수, outOfMem 설치
Widget::set_new_handler(outOfMem);
// 메모리 할당이 실패하면 outofMem이 호출
Widget* pw1 = new Widget;
// 할당 실패 시 전역new처리자 함수 호출
string* ps = new string;
//Widget 클래스만을 위한 new처리자 함수가 없도록 null로 설정
Widget::set_new_handler(0);
//이제 실패 시 바로 예외를 던짐 new처리자 함수 null
Widget* pw2 = new Widget;
이 코드를 다른 클래스에서도 재사용 하기위해 믹스인 양식의 기본 클래스를 쓴다. 즉, 다른 파생 클래스들이 한 가지의 특정 기능만을 물려받아 갈 수 있도록 설계된 기본 클래스를 만들면된다.
지금의 경우 특정기능은 클래스별 new처리자를 설정한는 기능이고 이것이 포함된 기본 클래스를 템플릿으로 탈바꿈 시킨다.
// 클래스별 set_new_handler를 지원한느 "믹스인 양식"의 기본 클래스
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);
}
// 클래스 별로 만들어지는 currentHandler 멤버를 널로 초기화한다
template<typename T>
new_handler NewHandlerSupport<T>::currentHandler = 0;
class Widget : public NewHandlerSupport<Widget>
{
...
};
NewHandlerSupport<Widget> 으로부터 Widget이 상속받는 것을 신기하게 반복되는 템플릿패턴 CRTP라고 한다.
1993년까진 operator new가 메모리 할당을 할 수 없을 때 널 포인터를 반환하라고 했지만 이제는 bad_alloc 예외를 던지도록 명세가 바뀐다. 하지만 이걸 포기하기 싫었던 C++표준화 위원회는 전통적인 할당-실패-시-널-반환 으로 동작하는 형태의 예외불가 형태를 내놓았다.
class Widget { ... };
// 할당 실패 시 bad_alloc 예외 던짐
Widget* pw1 = new Widget;
// 그러면 이 코드는 반드시 실패
if (pw1 == 0) ...
// 할당하다 실패하면 널 반환
Widget* pw2 = new (nothrow) Widget;
//이 점검 코드는 성공할 수 도 있음
if (pw2 == 0) ...
예외불가 new는 그때 호출되는 operator new 에서만 예외가 발생되지 않도록 보장할 뿐, new (std::nothrow) widget등의 표현식에서 예외가 나오지 않게 막아준다는 이야기는 아니다.
중요포인트!
set_new_handler 함수를 쓰면 메모리 할당 요청이 만족되지 못했을 때 호출되는 함수를 지정할 수 있다.
예외불가(nothrow) new는 영향력이 제한되어 있다. 메모리 할당 자체만 적용되기 때문. 이후 호출되는 생성자에서는 얼마든지 예외를 던질 수 있음