Coding Memo
메모리 할당 - STL Allocator 본문
본 포스팅은 인프런에 등록되어 있는 Rockiss 님의 강의를 보고 간단하게 정리한 글입니다.
STL에 있는 많은 Containers 들은 선언할 때 추가적으로 allocator를 따로 지정해줄 수 있다. 이 allocator을 사용자 정의하여 각 container들의 메모리 사용하는 정책을 지정해 줄 수 있는 것이다.
예를 들어 vector 같은 경우 다음과 같은 생성자가 구현이 되어 있다.
template <class _Ty, class _Alloc = allocator<_Ty>>
class vector
...
그렇다면 이 allocator에는 어떤 타입이 들어가야 할까?
main 함수에서 다음의 코드를 실행해 본다면 무엇이 필요한지 대강 알 수 있다.
(BaseAllocator는 전에 작성했던 클래스 이다.)
int main()
{
vector<int, BaseAllocator> v;
}
실행하면 오류가 쭉 나오게 된다.
여기서 필요한 것들을 하나씩 구현해나가면 될 것이다.
먼저 식별자부터 하나 추가해보자: value_type을 typename으로 지정
template<typename T>
class StlAllocator
{
public:
using value_type = T;
};
오류가 상당히 줄어든 것을 알 수 있다.
메모리를 할당할 함수와 해제할 함수도 추가 한다. (vector에서 allocator 타입을 그대로 사용하는 것이므로 함수이름은 반드시 똑같이 해야 한다!)
"Allocator.h"
template<typename T>
class StlAllocator
{
public:
using value_type = T;
StlAllocator() {}
template<typename Other>
StlAllocator(const StlAllocator<Other>&) {}
T* allocate(size_t count)
{
const int size = static_cast<int>(count * sizeof(T));
return static_cast<T*>(xxalloc(size));
}
void deallocate(T* ptr, size_t count)
{
xxrelease(ptr);
}
};
이렇게 StlAllocator를 작성하면 빌드가 오류없이 된다.
참고: xxalloc과 xxrelease는 전에 작성했던 DEBUG 상태에 따라서 StompAllocator나 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 // _DEBUG
이제 사용할 Container들에 대해서 우리가 원하는 방식의 메모리 할당 방법을 사용하도록 바꿔 줄 수 있다.
vector 뿐만 아니라, list, map, set, queue, stack, deque 등에 사용할 container에 방금 작성한 StlAllocator 방식을 이용하도록 타입을 새롭게 정의해보자.
(메모리 관련 타입만 지정하고 나머지-hash, 비교 방법 등은 기존 방식 그대로 사용하면 된다.)
"Container.h"
template<typename Type>
using Vector = vector<Type, StlAllocator<Type>>;
template<typename Type>
using List = list<Type, StlAllocator<Type>>;
template<typename Key, typename Type, typename Pred = less<Key>>
using Map = map<Key, Type, Pred, StlAllocator<pair<const Key, Type>>>;
template<typename Key, typename Pred = less<Key>>
using Set = set<Key, Pred, StlAllocator<Key>>;
template<typename Type>
using Deque = deque<Type, StlAllocator<Type>>;
template<typename Type, typename Container = Deque<Type>>
using Queue = queue<Type, Container>;
template<typename Type, typename Container = Deque<Type>>
using Stack = stack<Type, Container>;
template<typename Type, typename Container = Vector<Type>, typename Pred = less<typename Container::value_type>>
using PriorityQueue = priority_queue<Type, 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 Key, typename Type, typename Hasher = hash<Key>, typename KeyEq = equal_to<Key>>
using HashMap = unordered_map<Key, Type, Hasher, KeyEq, StlAllocator<pair<const Key, Type>>>;
template<typename Key, typename Hasher = hash<Key>, typename KeyEq = equal_to<Key>>
using HashSet = unordered_set<Key, Hasher, KeyEq, StlAllocator<const Key>>;
테스트로 생성자와 소멸자가 제대로 호출되는지 확인해보자.
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);
}
결과
생성자와 소멸자가 제대로 호출 되는 것을 확인 할 수 있다.
* Map에 대해 값을 줄 때, m[10] = Unit(1000) 부분을 순서대로 실행시켜 보았더니 m[10] = Unit(1000)이 좀 특이하게 작동하는 것을 알 수 있었다.
Unit(1000) 에 대한 생성자 Unit(hp)가 호출이 되고,
m[10]에 대한 생성자 Unit()이 호출이 되고,
m[10]에 Unit(1000)을 넣은 후,
Unit(1000)에 대한 소멸자가 호출이 된다. (~Unit(1000))
이후 main 함수가 끝나면서 Map에 있는 hp가 1000인 Unit이 소멸되면서 ~Unit(1000)이 한번 더 호출 된다.
이후 차례대로 소멸자 호출...
'Game Server (C++)' 카테고리의 다른 글
Object Pool (0) | 2022.10.11 |
---|---|
Memory Pool (0) | 2022.10.09 |
메모리 할당 - Stomp Allocator (0) | 2022.10.06 |
메모리 할당 - Allocator (0) | 2022.10.06 |
Reference Counting (0) | 2022.09.30 |