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()
{ ... }
...
};
'Tutorials > C++ : Advanced' 카테고리의 다른 글
Effective C++ | 항목 54 : TR1 을 포함한 표준 라이브러리 구성요소와 편안한 친구가 되자 (0) | 2022.10.26 |
---|---|
Effective C++ | 항목 53 : 컴파일러 경고를 지나치지 말자 (0) | 2022.10.26 |
Effective C++ | 항목 51 : new 및 delete 를 작성할 때 따라야 할 기존의 관례를 잘 알아 두자 (0) | 2022.10.26 |
Effective C++ | 항목 50 : new 및 delete 를 언제 바꿔야 좋은 소리를 들을지를 파악해 두자 (0) | 2022.10.25 |
Effective C++ | 항목 49 : new 처리자의 동작 원리를 제대로 이해하자 (0) | 2022.10.25 |
Comments