할당자 규칙
본래 할당자는 정의된 메모리의 모델의 포인터와 참조자에 대한 typedef 타입을 제공하는 것으로 의미가 있었음
디폴트 할당자는 allocator<T>::pointer, allocator<T>::reference라는 typedef 타입을 제공하고 사용자 정의 할당자도 이런 타입을 제공하도록 정해짐
C++에서 참조자를 흉내내려면 operator.를 오버로딩해야 하는데 언어적으로 허용되지 않기 때문에 프록시 객체를 사용
할당자는 객체이므로 멤버 함수, nested 타입과 typedef 타입 등을 가질 수 있음
표준안에 의하면 같은 타입의 모든 할당자 객체는 동등하며 항상 상등 비교를 수행하도록 STL을 구현해야 함
splice는 리스트 요소를 다른 리스트 요소에 옮길 때는 포인터 몇 개가 바뀌면서 복사가 일어나지 않기 때문에 빠르고 예외 안전성을 갖추고 있음
L1이 소멸될 때 L1이 가지고 있는 모든 노드를 소멸시키는데 L1의 할당자가 L2의 할당자에 의해 할당된 노드를 해제해야 하므로 같은 타입의 모든 할당자 객체는 동등하다고 가정하는 것이 설명이 됨
template<typename T>
class SpecialAllocator {...};
typedef SpeicalAllocator<Widget> SAW;
list<Widget, SAW> L1;
list<Widget, SAW> L2;
L1.splice(L1.begin(), L2); // L2의 노드를 L1의 앞에 옮김
할당자의 타입이 동등해야 한다는 제약은 다른 STL 코드에서도 제대로 동작하는 할당자이므로 객체가 상태를 가지지 않는다는 것
상태를 가진다는 것은 동작 원리에 조금이라도 영향을 주기 때문에 STL은 동등하지 않은 할당자를 사용했을 때 런타임 데이터 구조가 더럽혀질 수 있음
컴파일이 잘 되므로 할당자가 동등하도록 만드는 것이 프로그래머의 책임
할당자는 저수준 메모리 할당을 수행한다는 점에서 operator new와 같지만 인터페이스는 다름
operator new는 바이트 수를 받는 반면 allocate는 객체의 개수를 받음
operator new는 초기화되지 않은 메모리의 포인터를 나타내는 void*를 반환
allocate는 T*(pointer)를 반환하는데 T*는 T가 만들어지지 않았기 때문에 T 객체를 가리키지 않고 T 객체 크기만큼의 메모리 공간을 가리킨 후 추후 T 객체를 생성(reserve는 제외)
void* operator new(size_t bytes);
pointer allocator<T>::allocate(size_type numObjects);
list, 표준 연관 컨테이너(set, multiset, map, multimap)은 생성될 때 할당자를 한 번도 호출하지 않음
모두 노드 기반으로 값이 새로 저장될 때마다 새로운 노드가 동적으로 할당되는 자료 구조에 기반을 둠
필요한 것은 T가 아니라 T를 담고 있는 ListNode이므로 Allocator가 필요 없음
template <typename T, typename Allocator = allocator<T>>
class list
{
private:
Allocator alloc;
struct ListNode
{
T data;
ListNode* prev;
ListNode* next;
};
};
할당자 템플릿은 무엇이 되든 rebind nested 구조체 템플릿을 가져야 함
결과적으로 list<T>는 Allocator::rebind<ListNode>::other를 참조하여 ListNode의 할당자를 찾음
따라서 커스텀 할당자를 만들 때에는 rebind 템플릿을 제공해야 한다는 규칙을 알고 있어야 함
template <typename T>
class allocator
{
public:
template <typename U>
struct rebind
{
typedef allocator<U> other;
};
};
// ListNode의 할당자
Allocator::rebind<ListNode>::other
커스텀 할당자를 작성할 때 기억해야 할 것
할당자를 템플릿으로 만듦, 템플릿 매개 변수에는 메모리를 할당하고자 하는 객체의 타입을 나타내는 T를 사용
pointer와 reference라는 typedef 타입을 제공하되, 항상 pointer는 T*, reference는 T&이도록 해야 함
할당자에는 객체별 상태가 없어야 함, 일반적으로 비정적 데이터 멤버를 가질 수 없음
할당자의 allocate 멤버 변수에는 필요한 객체의 개수(바이트 수가 아님)를 매개 변수로 넘김
allocate 함수는 pointer(T*)를 반환하지만 T 객체는 아직 생성되지 않았을 수 있음
표준 연관 컨테이너에서 필요로 하는 rebind라는 nested 템플릿 구조체를 꼭 제공
'C++ > Effective STL' 카테고리의 다른 글
Effective STL 항목 12 STL 컨테이너의 스레드 안전성에 대한 기대는 현실에 맞추어 가지자 (0) | 2024.12.23 |
---|---|
Effective STL 항목 11 커스텀 할당자를 제대로 사용하는 방법을 이해하자 (0) | 2024.12.22 |
Effective STL 항목 9 데이터를 삭제할 때에도 조심스럽게 선택할 것이 많다 (0) | 2024.12.19 |
Effective STL 항목 7 new로 생성한 포인터의 컨테이너를 사용할 때에는 컨테이너가 소멸되기 전에 포인터를 delete하는 일을 잊지 말자 (0) | 2024.12.18 |
Effective STL 항목 6 C++ 컴파일러의 어이없는 분석 결과를 조심하자 (0) | 2024.12.17 |