정체불명의 모모
[C++] std::allocator<T> 클래스 본문
c++ 표준 allocator에 대해 알아보겠습니다.
커스텀 allocator를 알아보기 전에 표준에 대해 더 자세히 알아갈 필요가 있다고 느꼈습니다.
ㅁ <memory> allocator<T>
: 일반적으로 c++에서 메모리를 동적으로 할당하고 해제 할 때 new / delete 연산자를 사용하는데
allocator클래스는 주로 라이브러리 작성할 때, 특히 표준 라이브러리의 컨테이너를 구현할 때 많이 사용 됩니다.
할당자는 *fine-grained(정확한 개념은 모르겠습니다.) 방식 즉, 메모리 관리를 좀 더 세밀하게 컨트롤 해야 하고
유연하고 효율적으로 사용해야 할 경우에 유저가 원하는 메모리 할당 방식으로 구현할 수 있습니다.
allocator 클래스를 상속받아 멤버 함수를 override 해서 커스텀 할 수 있습니다.
컨테이너는 메모리를 최대한 효율적으로 관리할 수 있어야 하는데 new / delete 연산자로는 세밀한 메모리 관리 능력이
떨어집니다. 컨테이너는 주로 데이터를 저장하고 관리하는 공간입니다.
따라서 저장 공간의 생성, 확장 ,추가, 삭제 등이 빈번 하게 발생합니다.
▷ 만약 위 과정에서 new / delete 연산자를 사용할 경우 문제점 (new 연산자를 사용하게 되면 따라오는 조건)
1. 기본 생성자 필요
2. 메모리 할당
3. 모든 요소 초기화
위 3가지 조건이 세트로 따라오게 되는데 이는 요소가 많아질수록 컴퓨터 자원의 사용량이 상당히 증가하게 됩니다.
또한, new 연산자는 라이브러리 개발자가 원하는 메모리 할당 방식으로 커스터마이징 할 수 없습니다.
allocator 클래스를 사용하면 위 단계들을 각각 원할 때 사용할 수 있습니다.
▷ 대표적인 컨테이너인 vector
// CLASS TEMPLATE vector
template < class _Ty, class _Alloc = allocator<_Ty>>
class vector
{
// varying size array of values
}
: 우리는 벡터를 사용할때 타입 매개변수로 하나만을 전달합니다.
하지만, 사실 vector 는 기본 타입 매개변수로 아래와 같이 allocator가 하나 더 사용됩니다.
"위 처럼 특정 컨테이너에 최적화된 유연한 메모리 사용과 관리를 위해 대부분의 컨테이너들은 allocator를 사용합니다."
ㅁ allocator이 좋은 이유
1. 메모리를 동적 할당할 경우 초기화 되지 않은 공간(uninitialized)으로 메모리를 allocate 할 수 있습니다.
: new 연산자의 경우 메모리를 할당하고 기본적으로 값 또는 객체를 의무적으로 초기화 해줍니다.
이는 요소가 많을 수록 오버헤드가 높아지게 되고 초기화를 원치 않을 경우에도 불필요한 오버헤드가 발생하게 됩니다.
new 연사자의 경우 두번의 초기화를 하게 됩니다.(불필요한 초기화가 필수여서)
▷ 두번의 초기화가 일어남
new 연산자 사용시 : (객체 생성 할때 초기화) int a = 0; ->
(나중에 원하는 값 넣어 초기화) int a = 3;
▷ allocator의 멤버함수를 이용하면?
: 메모리의 할당은 되었지만 초기화 되지 않은 상태의 메모리의 시작 주소를 얻을 수 있습니다.
원래 초기화 되지 않은 메모리 공간에 객체를 직접 할당할 수 없습니다.
하지만 해당 클래스의 멤버 함수 또는 관련 함수가 초기화 되지 않은 공간에 객체를 저장할 수 있도록 해줍니다.
2. 할당받은 메모리에 객체를 생성 후 메모리 해제(deallocate) 없이 생성한 객체들을 소멸(destroy)시킬 수 있습니다.
: 즉 메모리의 재할당 없이 그 공간을 allocate 했던 초기 상태로 만들 수 있습니다.
new 연산자로 할당한 메모리 공간은 delete를 사용하면 메모리 공간이 해제가 되죠, 즉 하나의 과정을 세밀하게 나누어
컨트롤 할 수 있습니다.
- 또한, 해당 라이브러리는 할당받은 메모리 공간 중 객체가 생성된 공간과 아직 초기화되지 않은 공간을 알 수 있는 방법을
제공해 줍니다.
- allocate(size_t ) : 초기화되지 않은 메모리 공간을 할당하여 그 시작 주소를 반환하는 함수입니다.
- size_t : (매개변수는) 바이트 단위가 아닌 필요한 T 객체의 개수(n)이며, 인자로 전달된 개수 만큼 T타입의 객체를 충분히 할당
할 수 있는 공간을 만듭니다.
충분한 공간 ex ) 8 bytes(데이터형) * 5(개) + 4bytes = 44bytes 할당 (시작 주소 반환)
- size_t : (매개변수는) 바이트 단위가 아닌 필요한 T 객체의 개수(n)이며, 인자로 전달된 개수 만큼 T타입의 객체를 충분히 할당
- deallocate(T* , size_t) : 메모리 공간을 해제하는 함수입니다.
- T* : 포인터는 allocate로 할당했던 메모리의 시작 주소를 가리키는 포인터이며
- size_t : 인자의 개수
- construct(T* , const T&) : 초기화 되지 않은 공간에 요소를 저장하는 함수 입니다.
- T* : 포인터가 가리키는 위치에 레퍼런스로 받은 객체를 저장합니다.
초기화 되지 않은 공간에 *(간접 참조) 연산자를 사용하여 값을 대입할 경우 에러가 발생합니다. - const T& : 저장할 객체
- T* : 포인터가 가리키는 위치에 레퍼런스로 받은 객체를 저장합니다.
- destroy(T*) : 객체를 소멸시킵니다.
소멸과 메모리 해제는 다릅니다.
T 타입 포인터를 인자로 받으며 포인터가 가리키는 위치의 객체의 소멸자를 호출합니다
즉, 인자로 전달된 포인터가 가리키는 객체의 소멸자를 호출합니다.
destroy를 호출 하지 않고 deallocate를 호출할 경우 각 요소에 저장된 객체는 사라지겠지만
사라진 객체가 가리키던 객체는 그대로 메모리에 남아 있어 메모리 누수(memory leak)가 발생 할 수 있습니다. - uninitialized_copy( ) : 멤버 함수는 아니지만 자주 사용되는 관련된 함수 입니다.
STL 의 std::copy 함수와 비슷하며 입력 반복자 2개(first , last)와 순방향 반복자 1개(out)를 인자로 받습니다.
(first , last) 범위의 요소들을 out이 가리키는 위치에 순서대로 복사합니다.
그리고 이 함수는 복사가 완료된 위치의 다음 요소를 가리키는 포인터를 반환합니다.
ex) 1~5 위치에 복사 완료 했다면 6위치를 가리키는 포인터를 반환 합니다. - uninitialized_fill( ) : 주어진 범위의 공간을 3번째 인자로 주어진 값으로 채웁니다.
ex ) uninitialized_fill(first , last , val) // (first , last) 범위의 각 모든 요소에 val 을 저장 합니다.
https://woo-dev.tistory.com/51
위 내용은 제가 작성한 포스팅을 보면 더 쉽게 이해 되실 겁니다!
https://uncertainty-momo.tistory.com/47
'프로그래밍(c++)' 카테고리의 다른 글
[ C++] std::mutex를 통한 thread 동기화 (0) | 2021.08.05 |
---|---|
[C++] placement new - 전용 new (0) | 2021.08.05 |
[스마트 포인터] Shared_ptr 알아보기(구현) (0) | 2021.07.15 |
[ C / C++ ] malloc( ) 와 new 의 차이점 (0) | 2021.07.05 |
[c++ ] c++로 List 구현 (0) | 2020.09.06 |