KoreanFoodie's Study

Effective C++ | 항목 52 : 위치지정 new 를 작성한다면 위치지정 delete 도 같이 준비하자 본문

Tutorials/C++ : Advanced

Effective C++ | 항목 52 : 위치지정 new 를 작성한다면 위치지정 delete 도 같이 준비하자

GoldGiver 2022. 10. 26. 06:31

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

항목 52 : 위치지정 new 를 작성한다면 위치지정 delete 도 같이 준비하자

핵심 :

1. operator new 함수의 위치지정(placement) 버전을 만들 때는, 이 함수와 짝을 이루는 위치지정 버전의 operator delete 함수도 꼭 만들자. 이 일을 빼먹으면, 찾아내기도 힘들고 생겼다가 안 생겼다 하는 메모리 누출 현상을 경험하게 된다.
2. new 및 delete 의 위치지정 버전을 선언할 때는, 의도한 바도 아닌데 이들의 표준 버전이 가려지는 일이 생기지 않도록 주의하자.

 

아래 코드를 보자.

Widget *pW = new Widget;

위의 코드는 먼저 operator new 가 호출되고, 이후 Widget 의 생성자가 호출될 것이다. 그런데 Widget 생성자에서 예외가 발생하면 C++ 런타임 시스템은 방금 호출한 operator new 와 "짝이 맞는" operator delete 를 호출해 주어야 한다. 기본형은 다음과 같이 정의되어 있기에, 표준 형태의 new / delete 사용은 문제가 되지 않는다.

// 기본형 operator new
void* operator new(std::size_t size) throw(std::bad_alloc);

// 전역 유효범위에서의 기본형 시그니처
void operator delete(void* rawMemory) throw();
// 클래스 유효범위에서의 기본형 시그니처
void operator delete(void* rawMemory, std::size_t size) throw();

 

하지만 다음과 같이 추가적인 인자를 넘겨주는 커스텀 operator new (이런 것을 위치지정 - placement - 버전 이라고 부른다) 를 만들면, delete 가 호출될 때 맞는 짝을 찾을 수가 없어 어떤 operator delete 도 호출되지 않는다.

// 위치지정(placement) new (비표준 형태) 예시들
void* operator new(std::size_t size, void* pMemory) throw(std::bad_alloc);
void* operator new(std::size_t size, std::ostream& logStream) throw();

...

// cerr 를 ostream 인자로 넘김
// Widget 생성자에서 예외가 발생하면 메모리 누출!
Widget *pW = new (std::cerr) Widget;

즉, 메모리 누출을 막으려면 "짝이 맞는" operator delete 를 만들어 주면 된다!

class Widget
{
public:
	void operator delete(void* rawMemory) throw();

	static void* operator new(std::size_t size, void* pMemory) throw(std::bad_alloc);
	void operator delete(void* rawMemory, void* pMemory) throw();

	static void* operator new(std::size_t size, std::ostream& logStream) throw();	
	void operator delete(void* rawMemory, std::ostream& logStream) throw();
};

...

// 이제 메모리 누출 걱정이 없음!
Widget* pW = new (std::cerr) Widget;

// Widget 생성자에서 예외를 던지지 않으면?
// 아래 코드는 기본형의 operator delete 를 호출한다
delete pW;

즉, 커스텀 operator new 를 만들 때는, 첫째, 기본형의 operator delete 를 만들어야 하고, 둘째, 위치지정 new 와 같은 추가 매개 변수를 받는 operator delete 를 만들어야 한다.

 

마지막으로, 클래스 전용의 함수가 기존의 operator new 를 가리는 상황을 보자.

/* C++ 가 전역 유효범위에서 제공하는 operator new 의 표준 형태 */

void* operator new(std::size_t size) throw(std::bad_alloc); // 기본
void* operator new(std::size_t size, void* ptr) throw(); // 위치지정
void* operator new(std::size_t size, const std::nothrow_t& nt) throw(); // 예외불가

/* operator new 를 클래스 안에 선언하면 위의 표준 형태들이 전부 가져짐! */


class Base
{
public:
	// 아래 코드는 기존의 표준 operator new 를 가린다
	static void* operator new(std::size_t size, std::ostream& logStream) throw();	
	...
};

...

// 에러! 표준 형태의 전역 operator new 가 가려짐
Base *pb = new Base;

// Base 의 위치지정 new 를 호출!
Base *pb = new (std::cerr) Base;


class Derived: public Base
{
public:
	// 기본형 new 를 클래스 전용으로 다시 선언
	static void* operator new(std::size_t size) throw();
	...
};

...

// 에러! Base 의 위치지정 new 가 가려져 있음
Derived *pd = new (std::clog) Derived;

// 문제 없음
Derived *pd = new Derived;

 

다음의 기본형 클래스를 만들고 상속하는 방식으로 위치지정 new 버전의 확장을 용이하게 할 수 있다.

class StandardNewDeleteForms
{
public:
	// 기본형 new/delete
	static void* operator new(std::size_t size) throw(std::bad_alloc)
	{ return ::operator new(size); }
	static void operator delete(void* pMemory) throw()
	{ return ::operator delete(pMemory); }

	// 위치지정 new/delete
	static void* operator new(std::size_t size, void* ptr) throw()
	{	return ::operator new(size, ptr); }
	static void operator delete(void* pMemory, void* ptr) throw()
	{ return ::operator delete(pMemory, ptr); }

	// 예외불가 new/delete
	static void* operator new(std::size_t size, const std::nothrow_t& nt) throw()
	{ return ::operator new(size, nt); }
	static void operator delete(void* pMemory, const std::nothrow_t& nt) throw()
	{ return ::operator delete(pMemory, nt); }
};

// StandardNewDeleteForms 를 상속받아, 원하는 operator new/delete 추가
class Widget: public StandardNewDeleteForms
{
	// 표준 형태가 Widget 내부에서 보이도록 만듦
	using StandardNewDeleteForms::operator new;
	using StandardNewDeleteForms::operator delete;

	// 사용자 정의 위치지정 new/delete 추가
	static void* operator new(std::size_t size, std::ostream& logStream) throw(std::bad_alloc)
	{ ... }
	static void operator delete(void* pMemory, std::ostream& logStream) throw()
	{ ... }
	...
};

 

Comments