저번 글에도 작성했듯이, allocator는 다음과 같은 형식을 정의해야 한다.
using value_type = T;
using pointer = T * ;
using const_pointer = const T*;
using void_pointer = void*;
using const_void_pointer = const void*;
using size_type = size_t;
using difference_type = std::ptrdiff_t;
std::ptrdiff_t는 포인터 간의 차이를 담고있는 정수 유형이다 ( 쉽게 말해 그냥 typedef int ptrdiff_t 다. )
그리고 Allocator에서 추가로 구현해야하는 인터페이스가 있는데, 하나 하나 작성하며 설명해놓겠다.
Alloc() = default;
~cAlloc() = default;
allocator도 결국엔 template class 이기 때문에, 생성자와 소멸자를 작성해야하는데, allocator 가 하는 일은 T 타입의 포인터를 할당 해제 해주는 Factory 라고 생각하면 쉽다.
생성자 내에선 해줄 일이 없고, 다른 함수에서 작성한다.
struct rebind {
using other = cAlloc<U>;
};
rebind 는 어떤 종류의 객체를 얻기 위해 사용된다.
std::list와 A라는 타입을 예로들면, std::list<T,A> 할당자는 T를 할당하기 위한 A이지만,
실제로 내부에서 list는 노드 기반으로 가지고 있어야 한다.
이렇게 T타입이 아닌, 다른 타입으로도의 할당( node )이 필요해지게 되는데
이와 같은 요구사항을 충족하기 위해 rebind 를 가져야 할 것을 권고 하고 있다.
( 이는 C++17에서 사용중지 권고가 내려졌고, C++20에서 삭제 예정이다. )
template<class _Value_type,
class _Voidptr>
struct _List_node
{ // list node
using _Nodeptr = _Rebind_pointer_t<_Voidptr, _List_node>;
_Nodeptr _Next; // successor node, or first element if head
_Nodeptr _Prev; // predecessor node, or last element if head
_Value_type _Myval; // the stored value, unused if head
//중략
결과적으로 list<T>는 rebind<_List_node>::other를 참조함으로써, T 객체의 할당자를 통해 위의 List_node를 찾게 된다.
( 자세한 설명은 https://rookiecj.tistory.com/118 이곳을 보자 )
( 사실 C++17 부턴, rebind가 사용중지 권고가 내려져 정의하지않아도 컴파일은 잘 된다. )
template <typename U>
cAlloc(const cAlloc<U>& other) { }
다른 타입 (U) 에 대한 복사 생성자를 정의해준다. 이는 위에서 설명했다.
pointer allocate(size_type ObjectNum, const_void_pointer hint) {
allocate(ObjectNum);
}
pointer allocate(size_type ObjectNum) {
return static_cast<pointer>(operator new(sizeof(T) * ObjectNum));
}
초기화 되지 않은 곳의 n * sizeof(T) 바이트 만큼을 할당합니다.
지역참조를 위해 hint 라는 포인터를 사용할 수 있습니다. ( 구현자가 지원하는 경우 가깝에 만드려고 시도할 수 있음 )
( const_void_pointer 를 받는 allocate는 C++17부터 사용중지 권고가 내려졌다. )
void deallocate(pointer p, size_type ObjectNum) {
operator delete(p);
}
pointer에 의해 참조되는 곳을 할당 해제한다. deallocate가 삭제하는 곳은 꼭 allocate가 할당한 저장소여야 한다.
size_type max_size() const { return std::numeric_limits<size_type>::max() / sizeof(value_type);
이 container가 사용할 수 있는 최대 용량
template<typename U, typename... Args>
void construct(U *p, Args&& ...args) {
new(p) U(std::forward<Args>(args)...);
}
template <typename U>
void destroy(U *p) {
p->~U();
}
실제로 allocate 된 곳에 새로운 형태의 오브젝트를 생성하는 construct와 destroy 함수
construct 와 allocate는 container 의 reserve와 resize 를 생각하면 이해하기 쉬움
reserve : 생성자를 호출하지 않고 사이즈 만큼 공간을 예약 ( allocate )만 해둠
resize : 사이즈만큼 공간을 할당 ( allocate ) 하고, 사이즈만큼 생성자 ( construct ) 를 호출함
#pragma once
#include <limits>
template <typename T>
class cAlloc {
public:
using value_type = T;
using pointer = T * ;
using const_pointer = const T*;
using void_pointer = void*;
using const_void_pointer = const void*;
using size_type = size_t;
using difference_type = std::ptrdiff_t;
cAlloc() = default;
~cAlloc() = default;
template <typename U>
struct rebind {
using other = cAlloc<U>;
};
template <typename U>
cAlloc(const cAlloc<U>& other) { }
pointer allocate(size_type ObjectNum, const_void_pointer hint) {
allocate(ObjectNum);
}
pointer allocate(size_type ObjectNum) {
return static_cast<pointer>(operator new(sizeof(T) * ObjectNum));
}
void deallocate(pointer p, size_type ObjectNum) {
operator delete(p);
}
size_type max_size() const { return std::numeric_limits<size_type>::max() / sizeof(value_type); }
template<typename U, typename... Args>
void construct(U *p, Args&& ...args) {
new(p) U(std::forward<Args>(args)...);
}
template <typename U>
void destroy(U *p) {
p->~U();
}
};
이렇게 아무것도 추가되지 않은 첫 custom allocator가 만들어졌다.
#include <iostream>
#include <vector>
#include "cAlloc.h"
int main() {
std::vector<int, cAlloc<int>> v;
v.push_back(5);
std::cout << v.max_size();
}
'C++ > STL' 카테고리의 다른 글
remove-erase 고찰 (0) | 2021.05.03 |
---|---|
STL) 나만의 Allocator( 할당자 ) 만들기 - 1 (0) | 2019.04.16 |
std::list::splice (0) | 2019.04.12 |
STL) 반복자 어댑터 ( Iterator Adaptor ) (0) | 2019.04.12 |
STL) 분할, 정렬, 힙에 대한 추가 연산들 (stable_*, is_*, is_*_until) (0) | 2019.02.20 |