[Basic C++] #61_객체 풀, Obejct Pooling
C++의 "디자인 효율성"에 대해 알아보겠습니다.
"전문가를 위한 C"의 22 항목, "효과적인 메모리 관리"에 해당하는 내용입니다.
객체 풀링
1. 개념
- 객체가 필요할 때마다 직접 생성하는 것이 아니라, 시작 시점에 한 번만 생성됩니다.
- 필요할 때마다 객체 풀에 요청하며, 수명이 끝난 객체는 객체 풀에 되돌려 놓습니다.
- 즉, 객체의 생성자를 매번 필요할 때마다 호출하지 않고, 시작 시점에 미리 준비해 놓습니다.
2. 언제?
- 프로그램이 구동되는 동안 빈번하게 발생하는 "생성자"와 "소멸자" 호출을 피하기 위한 메커니즘.
- 개수가 많고, 비교적 수명이 짧은 타입의 객체들의 사용은 "객체 풀" 사용을 고려해야 합니다!
예제
1. 목표
- 객체들을 담을 컨테이너는 STL 컨테이너들 중 "Queue"를 활용합니다.
- 객체 풀에 정해진 개수만큼(MAX_POOL_SIZE) 객체를 할당합니다.
- 객체가 요구될 때마다 넘겨줍니다.
- 요청이 들어왔을 때, 객체 풀에 Extra 객체가 남아있지 않다면, 새로운 객체를 객체 풀에 할당합니다.
- 요청이 들어오면, 스마트 포인터를 통해 객체를 넘겨줍니다.
2. ObjectPool.h
#include <queue>
#include <iostream>
#include <memory>
#include <stdexcept>
#define MAX_POOL_SIZE 10
using namespace std;
template<typename T>
class ObjectPool
{
public:
// 1. 생성자
ObjectPool();
// 2. Type Aliasing
using Object = shared_ptr<T>;
// 3. 객체 할당 메서드
Object AcquireObject();
private:
// 4. 할당 가능한 객체 풀
queue<unique_ptr<T>> FreePool;
// 5. 할당 가능한 객체 개수
size_t currentPoolSize;
// 6. 할당 가능한 최대 객체 개수
static const size_t maxPoolSize = MAX_POOL_SIZE;
private:
// 7. 새로운 객체를 풀에 할당하는 메서드
void AllocateObject();
};
- 객체 풀 클래스를 템플릿 클래스로 정의합니다.
- 반환하는 객체를 "shared_ptr"로 감싸줍니다!
- 풀에 새로 할당하는 객체는 "unique_ptr"로 감싸줍니다!
3. ObjectPool.cpp
#include <queue>
#include <iostream>
#include <memory>
#include <stdexcept>
#define MAX_POOL_SIZE 10
using namespace std;
template<typename T>
class ObjectPool
{
public:
// 1. 생성자
ObjectPool(size_t initSize);
// 2. Type Aliasing
using Object = shared_ptr<T>;
// 3. 객체 할당 메서드
Object AcquireObject();
private:
// 4. 할당 가능한 객체 풀
queue<unique_ptr<T>> FreePool;
// 5. 할당 가능한 객체 개수
size_t currentPoolSize;
// 6. 할당 가능한 최대 객체 개수
static const size_t maxPoolSize = MAX_POOL_SIZE;
private:
// 7. 새로운 객체를 풀에 할당하는 메서드
void AllocateObject(int newObjectNum);
};
4. ObjectPool<T>::ObjectPool, 생성자 정의부
template<typename T>
ObjectPool<T>::ObjectPool(size_t initSize)
{
if (initSize == 0)
throw invalid_argument("Pool Size Can't be 0");
currentPoolSize = initSize;
// 최초 할당할 객체의 개수를 AllocateObject 메서드에 넘겨줍니다.
AllocateObject(crrentPoolSize);
}
5. ObjectPool<T>::Object ObjectPool<T>::AcquireObject, 객체 반환 메서드
template<typename T>
ObjectPool<T>::Object ObjectPool<T>::AcquireObject()
{
// Pool이 비어있다면, 새로운 객체를 할당합니다.
if (FreePool.empty() == true)
AllocateObject(1);
// 풀의 가장 앞에 있는 객체를 로컬 unique_ptr로 옮겨줍니다.
unique_ptr<T> objPtr(move(FreePool.front()));
FreePool.pop();
// 로컬 unique_tpr를 shared_ptr로 옮깁니다.* 주의 : 람다 표현식 활용 + 커스텀 제거자
// 커스텀 제거자 : 객체를 메모리에서 해제하지 않습니다!
// 람다 표현식 : 객체를 풀에 재 삽입합니다.
Object newObject(objPtr.release(), [this](T* t) {
FreePool.push(unique_ptr<T>(t));
});
return newObject;
}
- 간단하게 설명하자면, ObjectPool 에서 선언한 타입 에일리어싱, "using Object" 타입을 반환합니다.
- unique_ptr에서 shared_ptr로 옮기는 과정에서, 객체를 메모리에서 해제하지 않는 것이 주요합니다.
6. void ObejctPool<T>::AllocateObject, 객체 할당 메서드
template<typename T>
void ObjectPool<T>::AllocateObject(int newObjectNum)
{
for (int i = 0; i < newObjectNum; i++)
{
// emplace를 통해 즉석에서 객체를 생성해 삽입합니다.
FreePool.emplace(make_unique<T>());
}
}
객체 풀 활용
1. UserRequest.h
class UserRequest
{
public:
UserRequest();
virtual ~UserRequest();
// 1. 유저 요청 할당 메서드
// 2. 유저 요청 데이터 반환 메서드
//...
private:
//...
};
ObjectPool<UserRequest>::Object ObtainUserRequest(ObjectPool<UserRequest>& objPool)
{
auto request = objPool.AcquireObject();
return request;
}
2. Main
int main()
{
ObjectPool<UserRequest> requestPool(10);
for (size_t i = 0; i < 100; i++)
{
auto request = ObtainUserRequest(requestPool);
// 처리
}
return 0;
}
'언어 > Basic C++' 카테고리의 다른 글
[Basic C++] #22_참조형 변수 (0) | 2022.10.12 |
---|---|
[Basic C++] #28_static 키워드, 링킹, namespace (0) | 2022.10.09 |
[Basic C++] #60_스마트 포인터 (0) | 2022.09.28 |
[Basic C++] #59_가비지 컬렉션 (1) | 2022.09.27 |
[Basic C++] #58_포인터, 배열과 포인터, 포인터 연산, 함수 포인터, 클래스 메서드 포인터 (0) | 2022.09.22 |