template<class T, // 컨테이너 원소의 타입
class Allocator = std::allocator<T>> // ????? 어떤 기본 값을 갖는다.
class vector;
위와 코드는 vector
컨테이너 클래스의 원형이다. 해석하면 T 는 컨테이너에 저장될 원소의 타입을 말한다. 그런데, Allocator라는 알 수 없는 녀석이 있다. 이런식으로 어떤 맥락에서 알 수 없는 요소는 개념을 이해하기 불편하고, 글을 읽기 어렵게 만든다고 생각한다. 그래서, 먼저 Allocator에 대해 간략하게 설명하고자 한다.
1. Allocator란?
array를 제외한 모든 컨테이너는 동적으로 할당/해제되는 구조를 가졌다. 동적할당을 위해서는 할당과 해제에 관련된 연산이 필요한데, Allocator는 이 할당/해제 연산을 어떻게 할 것인가를 정의한 것이다. 컨테이너는 Allocator에 따라 자신에게 필요한 메모리를 할당하고, 해제한다.
우리는 정말 특수한 경우가 아니라면, Allocator를 직접 만들거나 신경 쓸 일은 없다. 왜냐하면, std에서는 기본적으로 사용할 수 있는 Allocator를 제공해주기 떄문이다. 위 Allocator의 기본값인 std::allocator<T>
가 바로 std에서 기본적으로 제공해주는 할당 구조체이다. std::allocator<T>
는 내부적으로 new와 delete 연산을 이용해서 할당/해제 연산을 수행한다. 따라서, 아래 내용은 생략하고 넘어가도 좋다.
2. 별도의 Allocator 필요한 경우?
그러나, Allocator가 밖으로 드러나 있다는 것은 직접 Allocator를 만들어야할 경우가 있다는 것이다. 아마 이런 경우를 생각해볼 수 있을 것이다. (상황이 딱히 생각이 안나서 chatGPT의 도움을 일부 빌렸다.)
- 어떤 레거시 코드에 이미 C-Style로 할당을 처리하는 경우
- 런타임에 돌아가는 가비지 콜렉터나, 직접 힙 메모리를 관리하는 경우
- 넉넉한 메모리를 우선 할당한 다음, 할당과 해제의 오버헤드를 줄이면서 직접 메모리를 관리해서 최적화 하거나, 별도의 GC를 직접 구현해 별도의 메모리 관리 정책이 있는 경우 이를 위한 별도의 Allocator를 작성해야할 수 있다.
- 기타 제약으로 인해서 allocator를 사용할 수 없을 때.
- 하드웨어 제약이나, 별도의 외부 메모리를 사용하고 이에 대한 로직이 따로 있는 경우 기본 allocator를 사용할 수 없기 때문에 어쩔 수 없이 Allocator를 만들어야 하는 경우가 있을 수 있다.
3. Allocator 정의
Allocator는 다음 4가지 연산 및 메서드를 반드시 구현해야 한다.
- T를 내부에서
value_type
로 다시 타입 정의 할 것 operator==
,operator!=
- 필수 연산이라고 되어있었지만, 단순한 vector 삽입 삭제에는 구현하지 않아도 문제 없었음.
allocator()
deallocator()
- 복사 생성자 (변환을 위한)
- MS 문서에는 필수라고 되어있지 않지만, 없는 경우 컴파일 에러 발생
MS 공식 문서에서 제공하는 C스타일 형식 Allocator 예제를 약간만 수정했다.
- Mallocator.h
// C-style Allocator
// https://learn.microsoft.com/en-us/cpp/standard-library/allocators?view=msvc-170#writing-your-own-allocator-c11
#pragma once
#include <stdlib.h> //size_t, malloc, free
#include <new> // bad_alloc, bad_array_new_length
#include <memory>
template <class T>
struct Mallocator
{
typedef T value_type; // value_type 정의
Mallocator() noexcept {}
// 변환을 허용하는 복사생성자
template<class U> Mallocator(const Mallocator<U>&) noexcept {}
T* allocate(const size_t n) const;
void deallocate(T* const p, size_t) const noexcept;
};
template <class T>
T* Mallocator<T>::allocate(const size_t n) const
{
if (n == 0)
{
return nullptr;
}
if (n > static_cast<size_t>(-1) / sizeof(T))
{
throw std::bad_array_new_length();
}
void* const pv = malloc(n * sizeof(T));
if (!pv) { throw std::bad_alloc(); }
return static_cast<T*>(pv);
}
template<class T>
void Mallocator<T>::deallocate(T* const p, size_t) const noexcept
{
free(p);
}
- main.cpp
#include<iostream>
#include<vector>
#include"Mallocator.h"
using namespace std;
int main() {
vector<int, Mallocator<int>> v1;
// 삽입 중에 할당 및 재할당과 해제 과정이 존재
v1.push_back(1);
v1.push_back(2);
v1.push_back(3);
v1.push_back(4);
v1.push_back(5);
for (auto& v : v1) {
cout << v << ' ';
}
v1.clear();
}