기존의 관례에 잘 맞는 operator new를 구현하려면 다음의 요구사항만큼은 기본으로 지켜야 합니다.
- 반환 값이 제대로 되어 있어야 합니다.
- 가용 메모리가 부족할 경우에는 new 처리자 함수를 호출(항목 49)
- 크기가 없는 (0byte) 메모리 요청에 대한 대비책
- 기본 형태의 new가 가려지지 않도록 해야합니다.
void* operator new(std::size_t size) throw(std::bad_alloc)
{
using namespace std;
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();
}
}
위 방법은 단일 스레드 환경이라면, 문제 없습니다.
다중스레드 환경에서는 new 처리자 함수를 둘러싼 자료구조들이 조작될 때 스레드 안전성이 보장되어야 하기 때문에 스레드 잠금을 걸어야합니다.
그리고 operator new 함수에는 무한 루프 가능성이 있습니다.
이 루프를 빠져나오는 조건은 메모리 할당이 성공 / 항목 49에서 이야기한 동작들 중 한 가지를 new 처리자 함수 쪽에서 제공
- 가용 메모리를 증가
- 다른 new 처리자를 설치
- new 처리자의 설치를 제거
- bad_alloc 혹은 bad_alloc에서 파생된 타입의 예외를 던짐.
- 프로그램 중단
반드시 해주지 않으면, operator new 내 무한 루프에 빠질 수 있음.
operator new 멤버 함수는 파생 클래스 쪽으로 상속이 되는 함수 -> 이 부분을 꼭 고려!
특정 클래스 전용의 할당자를 만들어서 할당 효율을 최적화하기 위해서 사용자 정의 메모리 관리자를 작성할 수 있음(항목50)
어떤 X라는 클래스를 위한 operator new 함수가 잇다면, 이 함수의 동작은 크기가 sizeof(X)인 객체에 대해 맞추어져 있는 것
상속 때문에 파생 클래스 객체를 담을 메모리를 할당하는 데 기본 클래스의 operator new 함수가 호출되는 경우가 발생
class Base
{
public:
static void* operator new(std::size_t) throw(std::bad_alloc);
...
};
class Derived: public Base // Derived에서는 operator new는 없음.
{...};
Derived *p = new Derived; // Base::operator new가 호출됨.
void* Base::operator new(std::siz_t size) throw(std::bad_alloc)
{
if(size != sizeof(Base)) // 의도된 크기가 들어오지 않으면, 표준 operator new 쪽으로 메모리 할당 처리하도록 넘깁니다.
return ::operator new(size);
...
}
C++ 에는 모든 독립 구조의 객체는 반드시 크기가 0이 넘습니다. 그래서 sizeof(Base)는 0이 절대 될 수 없습니다,
operator new[]를 직접 구현하는 것은 어렵습니다. operator new[] 안에서 해 줄 일은 단순히 원시 메모리의 덩어리를 할당하는 것
첫째, 객체 하나가 얼마나 큰지를 확정할 방법이 없습니다.
상속 때문에 파생 클래스 객체의 배열을 할당하는 데 기본 클래스의 operator new[] 함수가 호출될 수 있습니다.
그리고 파생 클래스 객체는 대체적으로 기본 클래스 객체보다 더 큽니다.
그래서 Base::operator new[]안에서조차도 배열에 들어가는 객체 하나의 크기가 sizeof(Base)라는 가정을 할 수 없음
둘째, size_t 타입의 인자는 객체들을 담기에 딱 맞는 메모리 양보다 더 많게 설정되어 있을 수도 있습니다.
동적으로 할당된 배열에는 배열 원소의 개수를 담기 위한 자투리 공간이 추가로 들어가는 경우가 있음.
operator delete를 작성할 때의 관례
C++는 널 포인터에 대한 delete 적용이 항상 안전하도록 보장한다는 사실만 잊지 않으면 됩니다.
class Base
{
public:
static void* operator new(std::size_t) throw(std::bad_alloc);
static void operator delete(void *rawMemory, std::size_t size) throw();
...
};
void Base::operator delete(void *rawMemory, std::size_t size) throw()
{
if(rawmemory == 0) return; // 널 포인터 점검
if(size != sizeof(Base)) // 의도된 크기가 들어오지 않으면,
// 표준 operator delete 쪽으로 메모리 제거 하도록 넘깁니다.
{
::operator delete(rawMemory); //
return;
}
...
}
가상 소멸자가 없는 기본 클래스로부터 파생된 클래스의 객체를 삭제하려고 할 경우에는 operator delete로 C++가 넘기는 size_t 값이 엉터리일 수 있음. 기본 클래스에 가상 소멸자를 꼭 두어야 하는 이유 중 하나
'서적 > Effective C++' 카테고리의 다른 글
항목 49: new 처리자의 동작 원리를 제대로 이해하자 (0) | 2022.07.18 |
---|---|
항목 36: 상속 받은 비가상 함수를 파생 클래스에서 재정의하는 것은 금물! (0) | 2022.04.07 |
항목 35: 가상함수 대신 쓸 것들도 생각해 두는 자세를 시시때때로 길러 두자 (0) | 2022.04.07 |
항목 34: 인터페이스 상속과 구현 상속의 차이를 제대로 파악하고 구별하자 (0) | 2022.04.07 |
항목 33: 상속된 이름을 숨기는 일은 피하자 (0) | 2022.04.07 |