52.위치지정 new를 작성한다면 위치지정 delete도 같이 준비하자
malloc과 new의 차이로 new는 생성자를 호출하고 초기화를 해준다는 점이 있다.
Widget* pw = new Widget;
위 코드는 함수 두 개가 호출된다.
1.operator new
2.Widget의 생성자
만약 생성자가 호출 중에 예외가 발생했다고 가정해보자. 동적 할당은 했는데 사용자는 메모리 해제할 방법이 없다.
pw의 포인터를 찾을 수 없기 떄문이다. 이 경우에 메모리 할당을 안전하게 되돌리는 일을 C++ 런타임 시스템이 해준다.
이때 C++ 런타임 시스템이 해야 할 일은 자신이 호출한 operator new 함수와 짝이 되는 버전의 operator delete 함수를 호출하는 것인데, 오버로드된 여러개의 operator delete 함수들 중에 어떤 operator delete를 호출해야 하는지 런타임 시스템이 정확하게 알아야한다.
위 코드에서는 표준 기본형 operator new를 사용했기 때문에 delete 역시 표준 기본형 operator delete가 필요할 것이다.
void* operator new(std::size_t) throw(std::bad_alooc);
void operator delete(void* rawMemory) throw(); //전역 유효범위의 delte
void operator delete(void* rawMemory, std::size_t size) throw(); //클래스 유효범위의 delete
표준 ne 및 delete의 경우엔 new 함수의 동작을 되돌릴 delete 함수를 찾아내는데 별 어려움이 없지만, 사용자 지정 new 함수를 사용한 경우 사용자 지정 delete 함수를 짝지어주어야 한다.
위치지정(placement) new
operator new 함수는 기본형과 달리 매개변수를 추가로 받는 형태로 선언할 수 있다.
class Widget
{
public:
//위치지정 new
static void* operator new(std::size_t size, std::ostream& logStream) throw(std::bad_alloc);
static void operator delete(void* pMemory, std::size_t size) throw();
};
int main()
{
Widget* pw = new(std::cerr) Widget;
system("Pause");
return 0;
}
위 코드에서 static void* operator new(std::size_t size, std::ostream& logStream)를 보면 매개변수로 logStream을 추가로 받고 있다. 이런 함수를 위치지정 new라고 한다.
근데 사실 위치지정이라고 하면 위치를 지정해준다 즉, 할당할 메모리의 위치를 지정해준다는 느낌이 강한데 위 코드에서 메모리의 위치를 지정한다는 느낌은 받지 않는다. 위치지정 new는 개념적으로 추가 매개변수를 받는 new라고 생각하면 된다.
위치지정 new라는 이름답게 메모리 위치를 나타내는 포인터를 매개변수로 받는 것이 C++ 표준 라이브러리에 구현되어 있다. <new>에 이미 구현되어 있고 vector의 경우 이 함수를 사용하고 있다.
void* operator new(std::size_t, void* pMemory) throw();
위치지정 new라고 이름이 지어진 이유는 이런 위치지정 new가 원조이기 떄문이다.
위치지정(placement) delete
위치지정 new를 구현했다면, 이 함수와 짝을 이루는 위치지정 delete를 구현해야한다.
operator new가 받아들이는 매개변수의 개수 및 타입이 똑같은 버전의 operator delete를 구현하면 된다.
class Widget
{
public:
//위치지정 new
static void* operator new(std::size_t size, std::ostream& logStream) throw(std::bad_alloc);
//위치지정 delete
static void operator delete(void* pMemory, std::ostream& logStream) throw();
static void operator delete(void* pMemory) throw();
};
위치지정 new와 짝이 되는 위치지정 delete를 선언했으므로, 이제 위치지정 new를 지나 생성자가 호출된 경우 위치지정 delete가 런타임 시스템에 의해 자동으로 호출된다.
만약 생성자에서 예외를 던지지 않았고, 명시적으로 메모리 해제를 하는 시점까지 왔다고 가정해보자.
int main()
{
Widget* pw = new(std::cerr) Widget;
delete pw; //기본형 operator delete 호출
system("Pause");
return 0;
}
메모리 해제 시 위지지정 delete는 절대로 호출되지 않는다. 위치지정 delete는 위치지정 new를 지나 생성자에서 예외를 던진 경우에만 사용되는 함수다.
정리하면, 위치지정 new 함수와 조금이라도 연관된 모든 메모리 누출을 예방하려면, 표준 형태의 operator delete를 기본으로 마련해두고, 예외를 대비하기 위해 위치지정 new와 똑같은 추가 매개변수를 받는 위치지정 delete도 마련해야 한다.
사용자 자신이 쓸 수 있다고 생각한 다른 new들을 클래스 전용의 new가 가리지 않도록 신경써야 한다.
바깥쪽 유효범위에 있는 어떤 함수의 이름과 클래스 멤버 함수의 이름이 같으면 바깥쪽 유효범위의 함수의 이름이 가려진다(33장 참고).
int main()
{
Widget* pb = new Widget; //에러
system("Pause");
return 0;
}
33장에도 정리했지만, 사용자 지정 new가 표준 new를 가려버려서 사용할 수 없는 경우 표준 operator new의 형태를 구현해야 한다. C++가 전역 유효 범위에서 제공하는 operator new의 형태는 세 가지가 표준이라는 점을 기억하자.
void* operator new(std::size_t size) throw(std::bad_alloc);
void* operator new(std::size_t size, void*) throw();
void* operator new(std::size_t size, const std::nothrow_t&) throw();
이 세가지 표준을 클래스에 구현해주면 된다. new에 짝지어줄 delete도 잊으면 안된다.
class Widget
{
public:
//기본형 new/delte
static void* operator new(std::size_t size) throw(std::bad_alloc)
{ return ::new_handler(size); }
static void operator delete(void* pMemory) throw()
{ return ::operator delete(pMemory); }
//위치지정 new/delte
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/delte
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); }
};
이것만은 잊지 말자
operator new 함수의 위치지정(placement) 버전을 만들 때는, 이 함수와 짝을 이루는 operator delete 함수의 위치지정 버전도 만들어야 한다. 만들지 않는다면 메모리 누출이 발생한다.
new 및 delete의 위치지정 버전을 선언할 때는, 의도한 바도 아닌데 이들의 표준 버전이 가려지는 일이 생기지 않도록 하자.