일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
Tags
- exception
- Effective c++
- 예외
- reference
- more effective c++
- c++
- lua
- virtual function
- 상속
- effective stl
- 오블완
- 메타테이블
- UE4
- 다형성
- Smart Pointer
- 반복자
- 루아
- 암시적 변환
- 함수 객체
- resource management class
- 영화 리뷰
- 참조자
- 비교 함수 객체
- 티스토리챌린지
- implicit conversion
- 언리얼
- 스마트 포인터
- 영화
- 게임
- operator new
Archives
- Today
- Total
스토리텔링 개발자
[Effective C++] 52. 위치지정 new / delete 본문
728x90
항목 52 : 위치 지정 new를 작성한다면 위치 지정 delete도 같이 준비하자.
new 중 기본 생성자에서 예외가 발생한다면?
Widget* pw = new Widget;
- 위 코드는 실행 중 두 개의 함수가 순차적으로 호출된다. (항목 16, 항목 17 참조)
- 메모리 할당을 위한 operator new 호출
- Widget의 기본 생성자 호출
- 만약 메모리 할당은 성공했으나, 기본 생성자에서 예외가 발생한다면?
- 이미 할당된 메모리를 취소해야 한다.
- 하지만 메모리에 대한 포인터가 pw에 할당되지 않고 예외가 발생했으므로, 사용자 코드에서는 메모리를 해제할 수 없다.
- 그러므로 C++ 런타임 시스템이 이 역할을 맡아야 한다.
- 이 상황에서 C++ 런타임 시스템이 하는 일은?
- 호출한 operator new 함수와 짝이 되는 operator delete를 호출하여 메모리를 해제한다.
- 결국 operator new와 짝이 맞는 operator delete가 중요한데..
- 기본형 new / delete에서는 어차피 이미 마련되어 있는 형태들이 있으므로 문제가 되지 않는다.
// 기본형 operator new
void* operator new(std::size_t) throw(std::bad_alloc);
// 기본형 operator delete
void operator delete(void* rawMemory) throw(); // 전역 유효범위용
void operator delete(void* rawMemory, std::size_t size) throw(); // 클래스 유효범위용
- 즉, 문제가 되는 것은 기본형이 아닌 형태의 operator new의 경우이다.
- 비기본형이란 다른 매개변수를 추가로 가지는 operator new를 뜻한다.
- 이 경우, 이 new에 어떤 delete를 짝맞춰야 하는지 C++이 알지 못한다!
class Widget
{
public:
...
// 비기본형 operator new 형태
static void* operator new(std::size_t size,
std::ostream& logStream) throw(std::bad_alloc);
// 기본형 operator delete 형태
static void operator delete(void* pMemory,
size_t size) throw();
...
};
위치지정 new(placement new)
- 추가 매개변수를 받는 new를 뜻한다.
- 위치지정 new 중 특히 유용한 경우가 있는데,
- 생성한 객체를 할당할 포인터를 매개변수로 받는 위치지정 new
생성한 객체를 할당할 포인터를 매개변수로 받는 위치지정 new
// pMemory 매개변수는 operator new가 생성한 객체를 할당할 목표 포인터이다.
void* operator new(std::size_t, void* pMemory) throw();
- 이 위치지정 new는 C++ 표준 라이브러리에 포함되어 있기까지 하다.( #include <new> )
- vector도 이 위치지정 new를 사용한다.
- 해당 벡터의 미사용 공간에 원소 객체를 생성하고자 할 때.
- 위치지정 new라는 용어 자체도 이 경우에서 시작되었기 때문에 바로 그런 이름이 된 것이다.
Widget 클래스 설계의 문제점
- 위 예시의 Widget 클래스는 메모리 누출을 유발할 수 있다.
// 위치지정 new를 호출한다.
Widget* pw = new (std::cerr) Widget;
// Widget 생성자에서 예외가 발생하면,
// 위치지정 delete를 찾을 수 없기 때문에 메모리가 누출된다!
- 런타임 시스템 쪽에서는 호출된 operator new가 어떻게 동작하는지 알아낼 방법이 없다.
- 그러므로 그저 짝이 되는 operator delete를 호출할 뿐이다.
- 짝이 되는 operator delete란?
- 매개변수의 개수 및 타입이 똑같은 버전의 operator delete.(즉, 위치지정 delete)
- 결국 이 경우, 짝이 되는 위치지정 delete가 없으므로 어떤 operator delete도 호출하지 않는다!!
- 즉 위치지정 new를 만들었다면 짝이 되는 위치지정 delete를 만들어줘야 한다.
class Widget
{
public:
...
static void* operator new(std::size_t size,
std::ostream& logStream) throw(std::bad_alloc);
static void operator delete(void* pMemory) throw();
static void operator delete(void* pMemory,
std::ostream& logStream) throw();
...
};
위치지정 delete와 기본형 delete
- 만일 생성자에서 예외가 발생하지 않았고, 사용자 코드의 delete 문에 다다르면 어떻게 될까?
Widget* pw = new (std::cerr) Widget; // 성공!
...
delete pw; // 이 경우 어떻게 될까?
- 이 상황에서는 위치지정 delete가 아닌 기본형 delete가 호출된다!!
- 위치지정 delete가 호출되는 경우는,
- 위치지정 new가 호출되고 난 후의 생성자에서 예외가 발생할 때 뿐이다.
- 즉, 위 상황에서는 절대로 위치지정 delete가 불리지 않는다는 뜻이다.
결론
- 위치지정 new를 만들었다면?
- 기본형 operator delete, 위치지정 delete 두 가지 버전이 모두 마련되어 있어야 한다.
유의 사항
- 바깥 유효범위 함수와 클래스 멤버 함수는 이름이 같으면, 그저 이름만 같아도 가려진다.(항목 33 참조)
// 전역 함수의 이름 가리기 문제
class Base
{
public:
...
// 이 new가 표준 형태의 전역 new를 가려버린다.
static void* operator new(std::size_t size,
std::ostream& logStream) throw(std::bad_alloc);
...
};
Base* pb = new Base; // 컴파일 에러!! 전역 operator new가 가려져있음.
Base* pb = new (std::cerr) Base; // 성공. 위치지정 new 호출.
// 상속에서도 이름 가리기 문제는 발생한다.
class Derived : public Base
{
public:
...
// 기본형 new를 클래스 전용으로 재선언한다.
static void* operator new(std::size_t size) throw(std::bad_alloc);
...
};
Derived* pd = new (std::clog) Derived; // 컴파일 에러!! Base의 위치지정 new가 가려져있음.
Derived* pd = new Derived; // 성공. Derived의 operator new 호출.
- 기본적으로 C++가 전역 유효범위에서 제공하는 operator new의 형태는 아래와 같다. (항목 49 참조)
void* operator new(std::size_t) throw(std::bad_alloc); // 기본형 new
void* operator new(std::size_t, void*) throw(); // 위치지정 new
void* operator new(std::size_t, const std::nothrow_t&) throw(); // 예외불가 new(항목 49 참조)
- 해결법
- 그냥 클래스 전용 버전이 전역 버전을 호출하도록 구현하면 된다.
- 하지만 역시 번거롭다.
- 번거로움을 제거한 해결법
- 기본 클래스를 만들고, 이 안에 new 및 delete의 기본 형태를 모두 넣어둔다.
- 그리고 위치지정 new / delete를 추가하고자 하는 클래스에서 이를 상속받는다.
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()
{
::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()
{
::operator delete(size, 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&) throw()
{
::operator delete(pMemory);
}
};
// 상속으로 전역 new / delete를 사용할 수 있도록 한다.
class Widget : public StandardNewDeleteForms
{
public:
// 표준 형태가 Widget 내부에 보이도록 만든다.
using StandardNewDeleteForms::operator new;
using StandardNewDeleteForm::operator 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();
...
};
728x90
'개발 > Effective C++' 카테고리의 다른 글
[Effective C++] 54. 모던 C++ 기능 (0) | 2024.07.29 |
---|---|
[Effective C++] 53. 컴파일러 경고 문제 (0) | 2024.07.29 |
[Effective C++] 51. operator new / delete 커스텀 관례 (0) | 2024.07.25 |
[Effective C++] 50. operator new / delete는 언제 커스텀해야 할까? (2) | 2024.07.24 |
[Effective C++] 49. new 처리자 (6) | 2024.07.23 |
Comments