1. Object Pool이란?

Object Pool은 동일한 타입의 객체를 메모리 풀로 재사용하도록 설계된 자료구조입니다. 각 객체 타입별로 메모리 풀이 따로 생성되어, 메모리 오염 및 관리 이슈를 줄이고 성능을 높일 수 있습니다.

📌 기존 Memory Pool이 객체의 크기 기준으로 풀을 구성했다면, Object Pool은 타입 기준으로 구성합니다.


2. 왜 Object Pool이 필요한가?

❌ 기존 Memory Pool의 한계

  • 다양한 크기의 객체들이 하나의 풀에 섞여 있음.
  • 특정 객체에서 메모리 오염이 발생해도, 어떤 객체가 문제인지 추적이 어렵다.

✅ Object Pool의 장점

  • 클래스마다 별도의 풀을 만들어 관리.
  • 문제가 발생한 경우, 해당 타입만 집중적으로 디버깅 가능.
  • 템플릿 static 특성을 활용해 타입별로 풀 인스턴스가 하나씩만 생성됨.

3. Object Pool 구현 원리

핵심 아이디어

  • 각 타입별로 static MemoryPool 생성
  • Pop으로 객체 생성, Push로 객체 반환
  • placement new와 소멸자 수동 호출 사용
  • shared_ptr 커스텀 deleter를 이용해 자동 관리 가능

4. ObjectPool.h 핵심 코드 분석

#pragma once
#include "Types.h"
#include "MemoryPool.h"

template<typename Type>
class ObjectPool
{
public:
    template<typename... Args>
    static Type* Pop(Args&&... args)
    {
#ifdef _STOMP
        MemoryHeader* ptr = reinterpret_cast<MemoryHeader*>(StompAllocator::Alloc(s_allocSize));
        Type* memory = static_cast<Type*>(MemoryHeader::AttachHeader(ptr, s_allocSize));
#else
        Type* memory = static_cast<Type*>(MemoryHeader::AttachHeader(s_pool.Pop(), s_allocSize));
#endif
        new(memory) Type(forward<Args>(args)...); // placement new
        return memory;
    }

    static void Push(Type* obj)
    {
        obj->~Type();
#ifdef _STOMP
        StompAllocator::Release(MemoryHeader::DetachHeader(obj));
#else
        s_pool.Push(MemoryHeader::DetachHeader(obj));
#endif
    }

    static shared_ptr<Type> MakeShared()
    {
        shared_ptr<Type> ptr = { Pop(), Push };
        return ptr;
    }

private:
    static int32 s_allocSize;
    static MemoryPool s_pool;
};

template<typename Type>
int32 ObjectPool<Type>::s_allocSize = sizeof(Type) + sizeof(MemoryHeader);

template<typename Type>
MemoryPool ObjectPool<Type>::s_pool{ s_allocSize };

✨ 핵심 포인트

  • template을 통해 타입마다 static 멤버 별도 생성
  • Pop()은 placement new로 객체 생성
  • Push()는 소멸자 호출 후 풀에 반납
  • _STOMP 정의 여부에 따라 StompAllocator 사용 가능

5. Stomp Allocator와의 통합

메모리 오염 디버깅에 특화된 StompAllocator를 사용할 수 있도록 #ifdef _STOMP 조건부 컴파일을 추가했습니다.

  • _STOMP가 정의되면: StompAllocator 사용
  • 정의되지 않으면: MemoryPool 사용

Memory.cpp, ObjectPool.h에 모두 적용되어 테스트 모드 전환이 유연해집니다.


6. shared_ptr을 통한 안전한 메모리 관리

문제점

Knight* k = ObjectPool<Knight>::Pop();
delete k; // ❌ 위험한 사용!

해결책

shared_ptr<Knight> sptr = ObjectPool<Knight>::MakeShared();
  • MakeShared()는 Pop과 Push를 묶어 shared_ptr을 생성해줍니다.
  • 소멸 시 자동으로 Push()가 호출되므로 메모리 관리가 안전합니다.

7. 테스트 예제

class Knight
{
public:
    int32 _hp = rand() % 1000;
};

int main()
{
    // 수동 방식
    Knight* knight = ObjectPool<Knight>::Pop();
    ObjectPool<Knight>::Push(knight);

    // 스마트 포인터 방식
    shared_ptr<Knight> sptr = ObjectPool<Knight>::MakeShared();
    shared_ptr<Knight> sptr2 = MakeShared<Knight>(); // 메모리 풀 기반
}

참고: MakeShared<Knight>()는 일반 MemoryPool을 사용하는 버전입니다.


profile
李家네_공부방

0개의 댓글