KoreanFoodie's Study

Effective C++ | 항목 51 : new 및 delete 를 작성할 때 따라야 할 기존의 관례를 잘 알아 두자 본문

Tutorials/C++ : Advanced

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

GoldGiver 2022. 10. 26. 06:29

C++ 프로그래머의 필독서이자 바이블인, 스콧 마이어스의 Modern Effective C++ 를 읽고 기억할 내용을 요약하고 있습니다. 꼭 읽어보시길 추천드립니다!

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

핵심 :

1. 관례적으로, operator new 함수는 메모리 할당을 반복해서 시도하는 무한 루프를 가져야 하고, 메모리 할당 요구를 만족시킬 수 없을 때 new 처리자를 호출해야 하며, 0바이트에 대한 대책도 있어야 한다. 클래스 전용 버전은 자신이 할당하기로 예정된 크기보다 더 큰(틀린) 메모리 블록에 대한 요구도 처리해야 한다.
2. operator delete 함수는 널 포인터가 들어왔을 때 아무 일도 하지 않아야 한다. 클래스 전용 버전의 경우에는 예정 크기보다 더 큰 블록을 처리해야 한다.

 

기본적인 요구사항을 반영한 operator new 함수를 의사 코드로 만들어 보면 다음과 같다.

// 커스텀 operator new 함수는 다른 매개변수를 추가로 가질 수 있다
void* operator new(std::size_t size) throw(std::bad_alloc)
{
	using namespace std;

	// 0 바이트 요청이 들어오면 1 바이트 요구로 간주하고 처리
	if (size == 0)
	{
		size = 1;
	}
    
    // 메모리 할당 실패 시 루프를 통해 다시 시도
	while (true)
	{
		size 바이트를 할당해 본다;
		if (할당에 성공)
		{
			return (할당된 메모리에 대한 포인터);
		}

		// 할당에 실패했을 경우, 현재의 new 처리자 함수가
		// 어느 것으로 설정되어 있는지 찾아낸다
		new_handler globalHandler = set_new_handler(0);
		set_new_handler(globalHandler);

		if (globalHandler) (*globalHandler)();
		else throw std::bad_alloc();
	}
}

항목 49에서, new 처리자 함수는 가용 메모리를 늘려 주던가, 다른 new 처리자를 설치하든가, new 처리자의 설치를 제거하든가, bad_alloc 혹은 bad_alloc 에서 파생된 타입의 예외를 던지든가, 아예 함수 복귀를 포기하고 중단을 시켜야 한다고 이야기했다. 이렇듯 new 처리자 함수가 4가지 중 하나를 택해야 하는 이유는, 위의 operator new 함수의 구현처럼 내부 루프를 끝낼 수 있도록 구현되어야 하기 때문이다.

 

operator new 멤버 함수는 파생 클래스 쪽으로 상속이 되는데, 얼만큼의 메모리를 할당할 것인지(size) 가 파생 클래스로 제대로 전달되지 않을 수 있다.

class Base
{
public:
	static void* operator new(std::size_t size) throw(std::bad_alloc);
};

class Derived: public Base {};

int main()
{
	// Base::operator new 가 호출!
	Derived *p = new Derived;	
}

위의 케이스는, 실제 할당하려는 클래스의 메모리 크기가 Base 에서의 operator new 사이즈와 다르면, 표준 operator new 를 호출하게 만듦으로써 해결할 수 있다.

class Base
{
public:
	static void* operator new(std::size_t size) throw(std::bad_alloc)
	{
		// 0 바이트도 점검된다. 왜냐하면 Base 크기는 무조건 0 이상으로 잡히기 때문
		if (size != sizeof(Base))
			return ::operator new(size);

		...
	}
};

operator new[ ] 의 경우, 객체의 갯수를 (요구된 바이트 수 / sizeof(Base)) 로 계산할 수 없고(파생 클래스에서 사용할 수 있으므로) 인자로 넘어가는 size_t 타입의 인자는 객체들을 담기에 딱 맞는 메모리 양보다 더 많게 설정되어 있을 수도 있다. 이런 어려움 때문에, operator new[ ] 구현은 조금 더 까다롭다.

 

operator delete 도 비슷하다. 다음 예시를 보자.

class Base
{
public:
	static void* operator new(std::size_t size) throw(std::bad_alloc);
	static void* operator delete(void* rawMemery, std::size_t size) throw();
};

void Base::operator delete(void *rawMemery, std::size_t size) throw()
{
	// 널 포인터 점검
	if (rawMemery == 0) return;

	if (size != sizeof(Base))
	{
		::operator delete(rawMemery);
		return;
	}

	rawMemery 가 가리키는 메모리 해제;

	return;
}

참고로, 가상 소멸자가 없는 기본 클래스로부터 파생된 클래스의 객체를 삭제하려고 하면 operator delete 로 C++ 가 넘기는 size_t 값이 엉터리일 수 있다!

 

Comments