요즘 면접에서 골치인게 가비지 컬렉션(garbage collection)이다. C#이나 자바에서 지원하는 더 이상 사용하지 않는 객체를 자동으로 소멸해주는 기능이다. C++에서는 지원하지 않기에 동적 할당을 하면 명시적으로 소멸해줘야 하는 귀찮음이 생긴다. 스마트 포인터를 사용해서 신경 안쓰고 할 순 있지만, 타이핑하는 것도 귀찮고 해서 그냥 동적 할당하는 편이다.
면접을 보다보면 전혀 생각치도 못한 질문을 받을 때가 많다. 벡터의 원소를 추가하는데 추가할 메모리에 다른 무언가가 이미 할당되어 있다면 어떤 일이 발생하는가? 와 같은 전혀 생각해본 적도 없고 일어난적도 없는 질문들이 날 힘들게 했다.
new 처리자(new-handler)
만약 new 연산자가 할당할 여유 메모리가 없을 때 어떤 반응을 할까? 정답은 예외를 던진다. 메모리 할당이 제대로 되지 못한 상황에 대한 반응으로, operator new가 예외를 던지기 전에, 사용자 쪽에서 지정할 수 있는 에러 처리 함수를 우선적으로 호출하게 되어 있는데, 이 에러 처리 함수를 new 처리자(new-handler)라고 한다.
사실 정확하게 맞는 표현은 아니라고 한다(51장 참고). 이와 같은 메모리 고갈 상황을 처리할 함수를 표준 라이브러리에는 set_new_handler(*주 1)라는 함수가 구현되어 있다.
namespace std
{
typedef void(*new_handler)();
new_handler set_new_handler(new_handler p) throw();
}
new_handler는 받는 것도 없고 반환하는 것도 없는 함수 포인터에 대해 typedef이다. 그리고 set_new_handler는 new_handler을 매개 변수로 받고 new_handler를 반환하는 함수이다. 더 나아가 throw()로 인해 이 함수는 어떤 예외도 던지지 않을 것이다.
set_new_hanlder가 받아들이는 new_handler 타입의 매개변수는 operator new가 할당하지 못했을 때 operator new가 호출할 함수의 포인터이다. 반환 값은 set_new_hanlder가 호출되기 바로 전까지 new 처리자로 쓰이고 있던 함수의 포인터이다.
new 처리자의 설계 방향
operator new가 메모리를 할당 하다 실패하면 new 처리자가 호출될 것이다. 사용자가 요구한 만큼의 메모리를 할당할 때 까지 new 처리자를 계속 호출한다. 상당히 비효율적이므로 new 처리자를 설계해야 할 때 다음 동작 중 하나를 꼭 해야한다.
테스트를 해봤는데
1.사용할 수 있는 메모리를 더 많이 확보한다.
operator new가 시도하는 이후의 메모리 확보가 성공할 수 있도록 하는 전략이다. 프로그램이 시작할 때 메모리 블록을 크게 하나 할당해 놓았다가 new 처리자가 가장 처음 호출될 때 그 메모리를 쓸 수 있도록 허용하는 방법도 그 중 하나이다.
2.다른 new 처리자를 설치한다.
현재의 new 처리자가 더 이상 가용 메모리르 확보할 수 없다 해도, 자기 몫까지 해 줄 다른 new 처리자의 존잴르 알고 있다면, 현재의 new 처리자는 제자리에서 다른 new 처리자를 설치할 수 있다. 쉽게 애기하면 현재의 new 처리자 안에서 set_new_hanlder를 호출한다.
3.new 처리자의 설치를 제거한다.
set_new_hanlder에 NULL 포인터를 넘긴다. new 처리자가 설치된 것이 없으면, operator new는 메모리 할당이 실패 했을 때 예외를 던진다.
4.예외를 던진다.
bad_alloc 혹은 bad_alloc에서 파생된 타입의 예외를 던진다. operator new에는 에러를 받아서 처리하는 부분이 없기 때문에, 이 예외는 메모리 할당을 요청한 원래의 위치로 전파된다.
5.복귀하지 않는다.
abort 혹은 exit를 호출한다.
클래스 타입에 따른 개별적인 new 처리자 호출
할당된 객체의 클래스 타입에 따라서 메모리 할당 실패에 대한 처리를 다르게 하고 싶을 수 있다.
class X
{
public:
static void outOfMemory();
};
class Y
{
public:
static void outOfMemory();
};
int main()
{
X* p1 = new X; //메모리 할당 실패 한 경우 X::outOfMemory 호출
Y* p2 = new Y; //메모리 할당 실패 한 경우 Y::outOfMemory 호출
system("Pause");
return 0;
}
C++에는 특정 클래스만을 위한 할당에러 처리자 기능이 없다. 해당 클래스에서 자체 버전의 set_new_hanlder 및 operator new를 제공하도록 직접 구현해야 한다.
class NewHandlerHolder
{
public:
explicit NewHandlerHolder(std::new_handler nh) //현재 new 처리자 획득
: handler(nh)
{}
~NewHandlerHolder() { std::set_new_handler(handler); }
private:
std::new_handler handler;
NewHandlerHolder(const NewHandlerHolder&); //복사 방지
NewHandlerHolder& operator=(const NewHandlerHolder&);
};
class Widget
{
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 currentHanlder;
};
std::new_handler Widget::currentHanlder = 0; //NULL로 초기화
std::new_handler Widget::set_new_handler(std::new_handler p) throw()
{
std::new_handler oldHandler = currentHanlder;
currentHanlder = p;
return oldHandler;
}
void* Widget::operator new(std::size_t size)
{
NewHandlerHolder h(std::set_new_handler(currentHanlder)); //Widget의 new 처리자 설정
return ::operator new(size); //메모리를 할당거나 실패하면 예외 던짐
}
void OutOfMem(); //new 처리자 함수 구현
int main()
{
Widget::set_new_handler(OutOfMem); //Widget의 new 처리자 함수로 OutOfMem함수 설정
Widget* pw1 = new Widget; //메모리 할당이 실패하면 OutOfMem 호출
string* ps = new string; //메모리 할당이 실패하면 전역 new 처리자 함수가 (있으면) 호출
Widget::set_new_handler(0); //Widget의 new 처리자 함수로 NULL 설정. 제거했다고 생각하면됨
Widget* pw2 = new Widget; //메모리 할당이 실패하면 예외 던짐(Widget 전용 new 처리자가 없음)
delete pw1;
delete ps;
delete pw2;
system("Pause");
return 0;
}
이것만은 잊지 말자
set_new_hanlder 함수를 쓰면 메모리 할당 요청이 만족되지 못했을 때 호출되는 함수를 지정할 수 있다.
예외불가(nothrow) enw는 영향력이 제한되어 있다. 메모리 할당 자체에만 적용되기 때문이다. 이후 호출되는 생성자에서는 예외를 던질 수 있다.
(*주 1) set_new_handler
new_handler set_new_handler(new_handler Pnew) throw();
@brief : Operator new가 메모리 할당 시도에 실패할 때 호출되는 사용자 함수를 설치 한다.
@param Pnew : 설치할 new_handler
@return val : 첫 번째 호출인 경우 0, 후속 호출인 경우 이전 new_handler
'서적 정리 > Effective C++' 카테고리의 다른 글
53.컴파일러 경고를 지나치지 말자 (0) | 2022.01.03 |
---|---|
52.위치지정 new를 작성한다면 위치지정 delete도 같이 준비하자 (0) | 2022.01.03 |
51.new 및 delete를 작성할 때 따라야 할 기존의 관례를 잘 알아 두자 (0) | 2022.01.02 |
50.new 및 delete를 언제 바꿔야 좋은 소리를 들을지 파악해 두자 (0) | 2022.01.01 |
48.템플릿 메타프로그래밍, 하지 않겠는가? (0) | 2021.12.26 |
47.타입에 대한 정보가 필요하다면 특성정보 클래스를 사용하자 (0) | 2021.12.25 |
46.타입 변환이 바람직한 경우에는 비멤버 함수를 클래스 템플릿 안에 정의해 두자 (0) | 2021.12.25 |
45."호환되는 모든 타입"을 받아들이는 데는 멤버 함수 템플릿이 직방! (0) | 2021.12.24 |
댓글