C++ Custom Allocator | "커스텀 할당자" 가이드

C++ Custom Allocator | "커스텀 할당자" 가이드

이 글의 핵심

C++ Custom Allocator에 대해 정리한 개발 블로그 글입니다. template<typename T> class MyAllocator { public: using value_type = T; T allocate(size_t n) { return…

Custom Allocator란?

STL 컨테이너 메모리 관리

template<typename T>
class MyAllocator {
public:
    using value_type = T;
    
    T* allocate(size_t n) {
        return static_cast<T*>(::operator new(n * sizeof(T)));
    }
    
    void deallocate(T* p, size_t n) {
        ::operator delete(p);
    }
};

// 사용
std::vector<int, MyAllocator<int>> vec;

기본 구조

template<typename T>
class Allocator {
public:
    using value_type = T;
    
    Allocator() = default;
    
    template<typename U>
    Allocator(const Allocator<U>&) {}
    
    T* allocate(size_t n) {
        return static_cast<T*>(::operator new(n * sizeof(T)));
    }
    
    void deallocate(T* p, size_t n) {
        ::operator delete(p);
    }
};

template<typename T, typename U>
bool operator==(const Allocator<T>&, const Allocator<U>&) {
    return true;
}

template<typename T, typename U>
bool operator!=(const Allocator<T>&, const Allocator<U>&) {
    return false;
}

실전 예시

예시 1: 로깅 할당자

template<typename T>
class LoggingAllocator {
public:
    using value_type = T;
    
    T* allocate(size_t n) {
        std::cout << "할당: " << n << " * " << sizeof(T) << " bytes" << std::endl;
        return static_cast<T*>(::operator new(n * sizeof(T)));
    }
    
    void deallocate(T* p, size_t n) {
        std::cout << "해제: " << n << " * " << sizeof(T) << " bytes" << std::endl;
        ::operator delete(p);
    }
};

int main() {
    std::vector<int, LoggingAllocator<int>> vec;
    vec.push_back(1);
    vec.push_back(2);
    vec.push_back(3);
}

예시 2: 풀 할당자

template<typename T>
class PoolAllocator {
    MemoryPool* pool;
    
public:
    using value_type = T;
    
    PoolAllocator(MemoryPool* p) : pool(p) {}
    
    template<typename U>
    PoolAllocator(const PoolAllocator<U>& other) : pool(other.pool) {}
    
    T* allocate(size_t n) {
        if (n != 1) {
            throw std::bad_alloc();
        }
        return static_cast<T*>(pool->allocate());
    }
    
    void deallocate(T* p, size_t n) {
        pool->deallocate(p);
    }
    
    template<typename U>
    friend class PoolAllocator;
};

int main() {
    MemoryPool pool(sizeof(int), 100);
    std::vector<int, PoolAllocator<int>> vec{&pool};
}

예시 3: 정렬 할당자

template<typename T, size_t Alignment = 64>
class AlignedAllocator {
public:
    using value_type = T;
    
    T* allocate(size_t n) {
        void* ptr = aligned_alloc(Alignment, n * sizeof(T));
        if (!ptr) {
            throw std::bad_alloc();
        }
        return static_cast<T*>(ptr);
    }
    
    void deallocate(T* p, size_t n) {
        free(p);
    }
};

int main() {
    // 64바이트 정렬
    std::vector<float, AlignedAllocator<float, 64>> vec;
}

예시 4: 통계 할당자

template<typename T>
class StatsAllocator {
    static inline size_t allocCount = 0;
    static inline size_t deallocCount = 0;
    static inline size_t bytesAllocated = 0;
    
public:
    using value_type = T;
    
    T* allocate(size_t n) {
        ++allocCount;
        bytesAllocated += n * sizeof(T);
        return static_cast<T*>(::operator new(n * sizeof(T)));
    }
    
    void deallocate(T* p, size_t n) {
        ++deallocCount;
        ::operator delete(p);
    }
    
    static void printStats() {
        std::cout << "할당: " << allocCount << std::endl;
        std::cout << "해제: " << deallocCount << std::endl;
        std::cout << "바이트: " << bytesAllocated << std::endl;
    }
};

할당자 요구사항

template<typename T>
class Allocator {
public:
    // 필수
    using value_type = T;
    
    T* allocate(size_t n);
    void deallocate(T* p, size_t n);
    
    // 선택
    template<typename U>
    Allocator(const Allocator<U>&);
    
    // 비교
    bool operator==(const Allocator&) const;
    bool operator!=(const Allocator&) const;
};

자주 발생하는 문제

문제 1: 상태

// 할당자는 상태 가질 수 있음
template<typename T>
class StatefulAllocator {
    MemoryPool* pool;  // 상태
    
public:
    StatefulAllocator(MemoryPool* p) : pool(p) {}
    
    // 복사 시 상태 공유
};

// 컨테이너 이동 시 할당자 고려

문제 2: 재바인딩

// 컨테이너는 다른 타입 할당 필요
// 예: vector<T>는 내부적으로 T* 할당

template<typename T>
class MyAllocator {
public:
    template<typename U>
    struct rebind {
        using other = MyAllocator<U>;
    };
};

문제 3: construct/destroy

// C++17 이전: construct/destroy 필요
template<typename T>
class OldAllocator {
public:
    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();
    }
};

// C++17 이후: 불필요 (std::allocator_traits 사용)

문제 4: 성능

// 할당자 호출 빈번
// - 최적화 중요
// - 인라인 권장

template<typename T>
class FastAllocator {
public:
    [[gnu::always_inline]]
    T* allocate(size_t n) {
        return pool.allocate(n);
    }
};

활용 패턴

// 1. 풀 할당자
std::vector<T, PoolAllocator<T>> vec{&pool};

// 2. 정렬 할당자
std::vector<float, AlignedAllocator<float, 64>> vec;

// 3. 로깅
std::vector<int, LoggingAllocator<int>> vec;

// 4. 통계
std::vector<int, StatsAllocator<int>> vec;

FAQ

Q1: Custom Allocator?

A: STL 컨테이너 메모리 관리.

Q2: 용도?

A:

  • 메모리 풀
  • 정렬
  • 로깅/통계

Q3: 요구사항?

A: allocate, deallocate, value_type.

Q4: 성능?

A: 인라인 최적화 중요.

Q5: C++17 변경?

A: construct/destroy 불필요.

Q6: 학습 리소스는?

A:

  • “Effective STL”
  • “C++ Primer”
  • cppreference.com

같이 보면 좋은 글 (내부 링크)

이 주제와 연결되는 다른 글입니다.

  • C++ Stack Allocator | “스택 할당자” 가이드
  • C++ Allocator | “메모리 할당자” 커스터마이징 가이드
  • C++ Memory Pool | “메모리 풀” 가이드

관련 글

  • C++ Allocator |
  • C++ Memory Pool |
  • C++ Stack Allocator |
  • C++ Algorithm Copy |
  • C++ Algorithm Count |