일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
Tags
- exception
- 참조자
- Smart Pointer
- reference
- 게임
- 언리얼
- 루아
- c++
- 티스토리챌린지
- 암시적 변환
- 영화 리뷰
- Effective c++
- 다형성
- 비교 함수 객체
- implicit conversion
- UE4
- 상속
- 오블완
- effective stl
- 반복자
- 함수 객체
- 메타테이블
- 영화
- virtual function
- 스마트 포인터
- resource management class
- more effective c++
- 예외
- operator new
- lua
Archives
- Today
- Total
스토리텔링 개발자
[Effective STL] 10. 할당자(Allocator)의 제약 사항 본문
728x90
항목 10. 할당자(allocator)의 일반적인 사항과 제약 사항에 대해 잘 알아두자
할당자의 기원
- 16비트 운영체제(DOS) 시절, 라이브러리 제작자들이 near, far 포인터 구분에 힘을 덜 쓸 수 있도록
- 메모리 모델의 추상층으로 개발된 것이다.
- 그처럼 STL 할당자 역시 객체 메모리 관리를 편하게 하기 위해 설계되었다.
STL 할당자의 문제
- 몇몇 부분에서 효율성 저하가 판명되었다.
- 사실상 operator new, operator new[]와 동일한 기능이지만, 인터페이스는 전혀 비슷하지 않다.
- 심지어 malloc과도 비슷하지 않다.
할당자의 제약 사항 1. pointer / reference 타입
- 할당자는 자신에게 정의된 메모리 모델의 포인터와 참조자에 대한 typedef 타입을 제공한다.(모던에서는 using)
- C++ 표준안에 따르면...
- 타입 T에 대한 디폴트 할당자(std::allocator<T>)의 경우 아래와 같은 typedef 타입을 제공한다.
- std::allocator<T>::pointer
- std::allocator<T>::reference
- 사용자 정의 할당자도 이런 형식으로 typedef 타입을 제공하도록 정해져 있다.
- 헌데, C++에서는 참조자를 흉내낼 수 있는 방법이 없다. reference 타입을 어떻게 제공해야 할까?
- 프록시 객체를 쓸 수 있겠지만, 그럼에도 문제점이 있다.(항목 18 참조)(MEC++ 항목 30 참조)
- 더군다나 pointer / refercne 타입을 잘 지정했다손 쳐도, T*, T&를 직접 사용해버리는 STL 구현 코드들에서는 의미가 없다.
할당자의 제약 사항 2. 동일한 타입의 할당자 객체간의 동등 제약
- 그리고 할당자는 객체이다.
- 멤버 함수, 중첩(nested) 타입, typedef 타입(pointer, reference 등)을 가질 수 있다는 뜻이다.
- 하지만 표준안에 의하면 같은 타입의 모든 할당자 객체는
- 동등(equivalent)하며 항상 상등 비교(compare equal)를 수행한다고 가정하고 구현되어야 한다고 한다.
-
template<typename T> class SpecialAllocator { ... }; typedef SpecialAllocator<Widget> SAW; // SpecialAllocator for Widget list<Widget, SAW> L1; list<Widget, SAW> L2; ... L1.splice(L1.begin()), L2); // L2의 노드를 L1의 앞으로 옮긴다.
- 이 상황에서 L1이 삭제된다고 하면?
- L1 기존의 노드들은 L1의 할당자로 해제가 된다.
- L2에서 온 노드들은? L1의 할당자로 해제되어도 되는가?
- 그렇기에 같은 타입의 모든 할당자는 동등해야 한다는 표준안이 있는 것이다.
- 타입 별 할당자 객체는 동등해야 한다는 제약은 무척 가혹하다.
- 이식 가능한 할당자(다른 STL 구현 코드에서도 제대로 동작하는 할당자) 객체는 상태를 가지면 안된다는 뜻이므로.
- 상태를 가지지 않는단 말은, 비정적(non-static) 데이터 멤버가 없어야 함을 뜻한다.
- 즉, 힙에서 메모리를 할당하는 SpecialAllocator<int>와 다른 힙에서 메모리를 할당하는 SpecialAllocator<int>는 동시에 존재할 수 없다.
- 이 두 할당자는 동등하다고 할 수 없기 때문이다.
- 그리고 이 동등 제약은 컴파일러가 잡아내 주지 않는다.
- 사용자가 전적으로 지키도록 노력해야 한다...
할당자의 제약 사항 3. 이질적인 인터페이스
- 동일하게 저수준 메모리 할당을 수행하는 operator new와 인터페이스가 전혀 다르다.
// operator new 인터페이스
// 매개변수로 바이트 수를 받는다.
// 리턴값으로 raw pointer를 제공한다.
void* operator new(size_t bytes);
// allocator 인터페이스
// 매개변수로 객체의 갯수를 받는다.
// 리턴값으로 T*를 제공한다.(pointer는 거의 항상 T*라고 봐도 좋다.)
// 헌데.. 아래에서 상세히 설명한다.
pointer allocator<T>::allocate(size_type numObjects);
- 매개변수의 차이
- sizeof(int) == 4인 플랫폼이 있다면,
- operator new에는 4를 넘겨야 한다.
- allocator<T>::allocate에는 1을 넘겨야 한다.
- sizeof(int) == 4인 플랫폼이 있다면,
- 리턴값의 차이
- allocator<T>::allocate에서는 T*를 반환한다.
- 하지만, 이는 사기극이나 마찬가지이다.
- 사실 리턴된 포인터는 T 객체를 가리키지 않는다.
- 왜냐하면 T는 아직 만들어지지조차 않았기 때문이다.
- 실제로 T 객체를 만드는 것은 allocator<T>::allocate를 호출하는 호출자(caller)이다.
할당자의 제약 사항 4. 컨테이너에서는 호출되지 않는 할당자
- 대부분의 표준 컨테이너는 자신이 생성될 때, 같이 붙어온 할당자를 한 번도 호출하지 않는다.
list<int> L; // list< int, allocator<int> >와 똑같다.
// 하지만 allocator<int>는 전혀 불리지 않는다.
set<Widget, SAW> S;
// 역시 SAW는 전혀 불리지 않는다.
- list의 구현을 잠깐 살펴보자면
template< typename T, typename Allocator = allocator<T> >
class list
{
pirvate:
Allocator alloc; // 타입 T의 객체에 대한 할당자
// 연결 리스트 내의 노드
struct ListNode
{
T data;
ListNode* prev;
ListNode* next;
};
...
}
- 리스트에 새 노드가 하나 추가되면 할당자에서 필요한 메모리를 떼어 와야 할 것이다.
- 헌데 리스트가 필요한 것은 T 크기의 메모리가 아니라 T를 담고 있는 ListNode에 대한 메모리이다!
- 근데 Allocator는 ListNode가 아니라 T를 할당해준다..
- 할당자 쪽에서 이런 일을 해주는 typedef 타입이 있긴 하다.
- other 라는 이름이다.
- 하지만, other는 rebind라는 구조체 안에 들어 있는 중첩 typedef 타입인데,
- rebind 자체 역시 할당자 클래스 안에 중첩되어 있고,
- 할당자 클래스는 템플릿이다.
template<typename T>
class allocator
{
public:
template<typename U>
struct rebind
{
typedef allocator<U> other;
};
...
};
// ListNode에 대한 템플릿 타입 매개변수를 지정해 주어야 하므로, 아래와 같아질 것이다.
// Allocator::rebind<ListNode>::other
- 결론적으로, 커스텀 할당자를 만들기로 마음 먹었고, 표준 컨테이너에서도 사용할 생각이라면
- 반드시 rebind 템플릿을 제공해야 한다.
- 표준 컨테이너는 이것이 있을 것으로 가정하기 때문이다.
정리
- 표준 컨테이너를 지원하는 커스텀 할당자를 만들 때는 아래 규칙을 따르자.
- 할당자를 템플릿으로 만들자.
- pointer와 reference라는 typedef 타입을 제공하되,
- 항상 pointer는 T*, reference는 T&이도록 하자.
- 확장성, 동등성을 보장하기 위해 할당자에는 비정적 데이터 멤버를 넣지 말자.
- 이질적인 할당자의 allocate 인터페이스를 숙지하자.
- 매개변수로는 객체의 갯수를 넘긴다.
- 리턴값으로는 T* 포인터(pointer typedef 타입)를 넘긴다.
- rebind 중첩 템플릿을 꼭 제공하자.
728x90
'개발 > Effective STL' 카테고리의 다른 글
[Effective STL] 12. 쓰레드 안전성 (0) | 2024.11.19 |
---|---|
[Effective STL] 11. 커스텀 할당자(Custom Allocator) (0) | 2024.11.18 |
[Effective STL] 9. 컨테이너별 요소 삭제 (0) | 2024.11.13 |
[Effective STL] 8. auto_ptr 금지 (0) | 2024.11.12 |
[Effective STL] 7. 컨테이너 요소 new, delete 짝 맞춤 문제 (0) | 2024.11.11 |
Comments