우리는 이전에 BaseAllocator, StompAllocator 등을 통해 메모리를 커스터마이징하여 할당/해제하는 방법을 배웠다.
하지만 벡터, 리스트, 맵과 같은 STL 컨테이너들은 기본적으로 new/delete를 내부적으로 사용하고 있다.
즉, 우리가 아무리 xnew, xdelete로 메모리를 잘 관리해도 STL 컨테이너 안의 동적 객체들은 여전히 기본 힙 할당 방식을 따른다.
그래서 필요한 것이 바로 👉 사용자 정의 STL Allocator 이다!
template<typename T>
class StlAllocator
{
public:
using value_type = T;
StlAllocator() {}
// 다른 타입에서 복사 생성 가능하게 하기 위한 생성자
template<typename Other>
StlAllocator(const StlAllocator<Other>&) {}
T* allocate(size_t count)
{
const int32 size = static_cast<int32>(count * sizeof(T));
return static_cast<T*>(xxalloc(size)); // 디버그 모드에 따라 Base/ Stomp 할당자 사용
}
void deallocate(T* ptr, size_t count)
{
xxrelease(ptr);
}
};
✅ 핵심 포인트
value_type: Allocator가 어떤 타입을 다룰지를 명시allocate(count): T 타입 객체를 count만큼 메모리 할당deallocate(ptr, count): 해당 포인터의 메모리 해제
🧠 xxalloc, xxrelease는 디버그 여부에 따라 Stomp 또는 BaseAllocator를 사용하는 매크로로 미리 정의되어 있어야 한다.
#ifdef _DEBUG
#define xxalloc(size) StompAllocator::Alloc(size)
#define xxrelease(ptr) StompAllocator::Release(ptr)
#else
#define xxalloc(size) BaseAllocator::Alloc(size)
#define xxrelease(ptr) BaseAllocator::Release(ptr)
#endif
STL 컨테이너들은 template <class T, class Allocator = std::allocator<T>> 형태로 되어 있다.
즉, 우리가 만든 StlAllocator<T>를 두 번째 인자로 넘겨주면 된다.
매번 쓸 때마다 vector<int, StlAllocator<int>> 이렇게 쓰면 불편하므로
using 키워드를 통해 타입을 미리 재정의해 두면 훨씬 편하게 사용할 수 있다.
template<typename T>
using Vector = vector<T, StlAllocator<T>>;
template<typename T>
using List = list<T, StlAllocator<T>>;
template<typename K, typename V, typename Pred = less<K>>
using Map = map<K, V, Pred, StlAllocator<pair<const K, V>>>;
template<typename K, typename Pred = less<K>>
using Set = set<K, Pred, StlAllocator<K>>;
template<typename T>
using Deque = deque<T, StlAllocator<T>>;
template<typename T, typename Container = Deque<T>>
using Queue = queue<T, Container>;
template<typename T, typename Container = Deque<T>>
using Stack = stack<T, Container>;
template<typename T, typename Container = Vector<T>, typename Pred = less<typename Container::value_type>>
using PriorityQueue = priority_queue<T, Container, Pred>;
using String = basic_string<char, char_traits<char>, StlAllocator<char>>;
using WString = basic_string<wchar_t, char_traits<wchar_t>, StlAllocator<wchar_t>>;
template<typename K, typename V, typename Hasher = hash<K>, typename KeyEq = equal_to<K>>
using HashMap = unordered_map<K, V, Hasher, KeyEq, StlAllocator<pair<const K, V>>>;
template<typename K, typename Hasher = hash<K>, typename KeyEq = equal_to<K>>
using HashSet = unordered_set<K, Hasher, KeyEq, StlAllocator<K>>;
이제 STL 컨테이너를 우리가 만든 Allocator 기반으로 간단히 Vector, Map, Queue 등으로 사용할 수 있다!
class Unit
{
public:
Unit() { cout << "Unit()\n"; }
Unit(int hp) : _hp(hp) { cout << "Unit(hp)\n"; }
~Unit() { cout << "~Unit(): " << _hp << "\n"; }
int _hp = 1;
};
int main()
{
Vector<Unit> v(5); // 생성자 호출 확인
Queue<Unit, Deque<Unit>> q;
q.push(Unit(100));
Map<int, Unit> m;
m[10] = Unit(1000);
}
🟢 출력 결과를 보면, 생성자와 소멸자가 모두 제대로 호출되고 있으며,
m[10] = Unit(1000)의 경우 복사 생성, 소멸이 어떻게 일어나는지도 관찰 가능하다.