💡 왜 STL Allocator가 필요할까?

우리는 이전에 BaseAllocator, StompAllocator 등을 통해 메모리를 커스터마이징하여 할당/해제하는 방법을 배웠다.
하지만 벡터, 리스트, 맵과 같은 STL 컨테이너들은 기본적으로 new/delete를 내부적으로 사용하고 있다.

즉, 우리가 아무리 xnew, xdelete로 메모리를 잘 관리해도 STL 컨테이너 안의 동적 객체들은 여전히 기본 힙 할당 방식을 따른다.

그래서 필요한 것이 바로 👉 사용자 정의 STL Allocator 이다!


🧱 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 컨테이너에 커스텀 Allocator 적용

STL 컨테이너들은 template <class T, class Allocator = std::allocator<T>> 형태로 되어 있다.
즉, 우리가 만든 StlAllocator<T>를 두 번째 인자로 넘겨주면 된다.

매번 쓸 때마다 vector<int, StlAllocator<int>> 이렇게 쓰면 불편하므로
using 키워드를 통해 타입을 미리 재정의해 두면 훨씬 편하게 사용할 수 있다.

📦 Container.h

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)의 경우 복사 생성, 소멸이 어떻게 일어나는지도 관찰 가능하다.


📌 STL Allocator의 이점

  • 🛠 STL 컨테이너도 커스텀 메모리 정책 적용 가능
  • 🔍 StompAllocator를 통해 오염된 메모리 접근 시 크래시로 빠르게 버그 확인 가능
  • 🧼 메모리 누수, Overflow/Underflow, Use-After-Free 방지 가능
  • 🧩 Debug/Release 설정에 따라 유연한 메모리 전략 분기 가능

profile
李家네_공부방

0개의 댓글