추상 데이터 타입 정의하기 Defining abstract data types Chapter 11 추상 데이터 타입 정의하기 Defining abstract data types
11장에서는… 지금까지는 11장에서 살펴볼 것은 9장에서는 새로운 타입을 정의하기 위한 기본적인 언어 기능에 대해서 살펴보았다 3장에서 작성한 Student_info 타입에는 Student_info 타입의 객체가 복사되거나, 대입되거나, 소멸될 때 어떤 일이 수행되어야 하는지가 명시되지 않았다. 11장에서 살펴볼 것은 클래스 작성자는 객체의 행동 양식에 대한 모든 것을 제어할 수 있어야 한다는 것이다. 직관적이고 사용하기 쉬운 타입을 만들기 위해서는 이러한 연산들을 제대로 정의하는 것이 얼마나 중요한지에 대해서도 느끼게 될 것이다. vector 클래스를 직접 작성해 보면서, 클래스를 설계하고 구현하는 방법을 이해한다. 표준 vector를 에뮬레이트(emulate)하는 것이 우리의 목표
11.1 Vec 클래스 클래스를 설계할 때, 제공할 인터페이스를 먼저 정한다. 여기서는 표준 vector 클래스에 대한 부분집합을 구현할 것이기 때문에, 먼저 vector를 어떻게 사용하는지에 대해 살펴보자. // 벡터 생성 vector<Student_info> vs; //빈 벡터 vector<double> v(100); // 100개의 요소를 갖는 벡터 // 벡터가 사용하는 타입의 이름을 얻는다 vector<Sutdent_info>::const_iterator b, e; vector<Sutdent_info>::size_type i=0; // 벡터의 각 요소를 살펴보기 위해, size 및 index 연산자 사용 for (i=0; i != vs.size(); ++i) cout << vs[i].name(); // 첫 번째 요소와 마지막 요소의 바로 다음에 대한 반복자를 리턴 b=vs.begin(); e=vs.end();
11.2 Vec 클래스 구현하기 연산들을 결정했으면, 이제 할 일은 Vec을 어떻게 나타내는지 고민.. 템플릿 클래스 정의 템플릿 클래스(template class)를 정의한다. 사용자들이 Vec에 어떠한 타입이라도 담을 수 있도록 함. 템플릿 클래스도 한번만 정의하면, 그 클래스를 사용해서 템플릿 매개변수 목록에 사용되는 타입만 다른 여러 버전의 클래스를 생성할 수 있다. 예) vector, list, map 등 템플릿 클래스 정의 Vec이 템플릿 클래스임 T라는 타입 매개변수를 갖는다 template <class T> class Vec { public: // 인터페이스 private: // 구현 };
구현- 어떤 데이터를 저장할 것인가 배열에 어떤 정보를 저장해야 할까요? Vec 안에 요소들을 저장할 공간이 필요하기 때문에, Vec가 포함하는 요소들의 개수를 기록해야 한다 가장 확실한 선택은 동적으로 할당된 배열 안에 요소들을 저장한다 배열에 어떤 정보를 저장해야 할까요? begin, end, size 함수 등을 구현 첫 요소, 마지막 요소 다음에 대한 포인터 저장 개수는 두 포인터를 이용해서 계산 template <class T> class Vec { public: // 인터페이스 private: T* data; // 첫번째 요소 T* limit;// 마지막 다음 }; Vec 객체의 요소들 data limit Vec Vec<int> v; Vec<string> vs;
메모리 할당 Vec 클래스는 배열을 동적으로 할당하기 때문에 라이브러리는 메모리 할당 클래스를 제공 new T[n] 을 사용 메모리 할당에 대한 세세한 컨트롤을 제공한다. 메모리를 관리하는 유틸리티 함수가 존재한다고 가정하고, 그 함수를 통해서 Vec 클래스를 완성하기로 하자! (11.5에서 자세히) Vec의 private 멤버로 들어갈 것이다. 이 멤버들은 필요한 메모리를 할당하고, 해제하고, 요소들을 초기화하고, 소멸시킬 책임이 있다. 따라서, data와 limit 포인터들을 관리해야 한다. Vec의 public 멤버들은 data와 limit을 읽기만 할 수 있다. 새로운 Vec을 생성하는 등의 작업을 할 때는 적당한 메모리 관리함수를 호출하도록 한다.
생성자 (Constructors) 생성자의 역할은 객체를 제대로 초기화시키는 것 Vec<Student_info> vs; // 디폴트 생성자. 빈 벡터 생성 Vec<Student_info> vs(100); // 사이즈를 취하는 생성자 Vec<int> vs(100, 0); // 초기화 생성자 생성자의 역할은 객체를 제대로 초기화시키는 것 data와 limit을 초기화 // 디폴트 생성자 Vec의 요소를 담을 공간을 확보하고, 초기화 (디폴트 인자 이용) template <class T> class Vec { public: Vec() { create(); } // create()는 11.5절에서 explict Vec(size_type n, const T& val = T() ){ create(n, val); } // create(100,0)는 11.5절에서 // 나머지 인터페이스 private: T* data; // 첫번째 요소 T* limit;// 마지막 다음 };
explicit 을 사용하면 하나의 인자를 취하는 생성자를 정의할 때에만 유효한 키워드 어떤 생성자가 explicit이라는 것은, 사용자가 explicit로 지정된 생성자를 직접 호출하는 경우에만 컴파일러가 그 생성자를 사용하고, 직접 호출하지 않는 경우에는 그 생성자를 사용하지 않는다는 것을 의미 Vec<int> vi(100); // OK. int를 통해 명시적으로 Vec생성 Vec<int> vi = 100; // ERROR 자세한 논의는 12.2절에서 표준 vector 클래스와의 일관성을 위해, 생성자를 explicit으로 만들었지만, 이장의 나머지 예제들은 그 점에 의존하지 않음!
타입 정의 표준 템플릿 클래스가 사용하는 용례를 따라.. Vec<int>::iterator it; 사용자가 사용할 타입 이름을 짓고, 어떻게 클래스를 구현했는지에 대한 자세한 구현내용은 감추고자 한다. Vec<int>::iterator it; Vec<int>::const_iterator its; Vec<int>::size_type i; template <class T> class Vec { public: typedef T* iterator; // 반복자 typedef const T* const_iterator; // const 반복자 typedef size_t size_type; // Vec의 크기를 나타내는 타입 typedef T value_type; // 컨테이너가 저장하는 객체 타입 Vec() { create(); } explict Vec(size_type n, const T& val = T() ){ create(n, val); } // 나머지 인터페이스 private: iterator data; // 변경됨 iterator limit; // 변경됨 };
인덱스 연산 및 size() 구현 size()를 호출하여 Vec안의 요소 개수를 확인 for(i=0; i != vs.size(); ++i) cout << vs[i].name(); size()를 호출하여 Vec안의 요소 개수를 확인 size함수는 Vec 클래스의 멤버가 되어야 함 Vec의 요소들은 배열에 저장되므로, 그 배열의 범위를 가리키는 두 포인터의 차를 구하면 요소들의 개수가 된다. 따라서, 요소의 개수는 limit – data 인덱스 연산 []을 사용하여 Vec안의 요소들을 접근 Vec안에 [] 연산자를 정의해야 함 오버로드(overloaded) 연산자 [] 연산자의 이름은 operator 단어 뒤에 연산자 기호를 추가. 따라서, 우리가 정의할 연산자는 operator []
오버로드(overloaded) 연산자 인자의 개수 리턴 타입: Vec에 저장된 요소에 대한 레퍼런스를 리턴해야 함 오버로드 연산자도 다른 함수를 정의하는 것과 같은 방식으로 정의한다: 이름을 가지며, 인자들을 취하고, 리턴 타입을 명시 연산자 함수는 멤버함수 일수도 있고, 비 멤버함수 일수도 있다 인자의 개수 연산자의 종류(즉, 단항연산자 또는 이항연산자)에 따라 결정 연산자가 멤버가 아닌 함수라면, 피연산자의 개수만큼 인자들을 갖는다. 첫번째 인자는 왼쪽 피연산자에 해당, 두번째 인자는 오른쪽 피연산자에 해당한다. 연산자가 멤버함수로 정의되었다면, 왼쪽 피연산자는 연산자를 호출하는 객체에 해당 연산자에 필요한 인자보다 하나 더 적은 인자를 갖는다. 리턴 타입: Vec에 저장된 요소에 대한 레퍼런스를 리턴해야 함 Vec의 값을 읽기만 하는 경우와 Vec의 요소에 값을 쓰는 기능
template <class T> class Vec { public: typedef T* iterator; // 반복자 typedef const T* const_iterator; // const 반복자 typedef size_t size_type; // Vec의 크기를 나타내는 타입 typedef T value_type; // 컨테이너가 저장하는 객체 타입 Vec() { create(); } explict Vec(size_type n, const T& val = T() ){ create(n, val); } // 새로운 연산: 인덱스 및 size size_type size() const { return limit – data; } T& operator[] (size_type i) { return data[i];} // 쓰기 const T& operator[] (size_type i) const { return data[i]; } // 읽기 private: iterator data; iterator limit; };
반복자를 리턴하는 연산: begin(), end() for (Vec<int>::iterator iter=students.begin(); iter != students.end(); ++iter) template <class T> class Vec { public: typedef T* iterator; typedef const T* const_iterator; typedef size_t size_type; typedef T value_type; Vec() { create(); } explicit Vec(size_type n, const T& t = T()) { create(n, t); } T& operator[](size_type i) { return data[i]; } const T& operator[](size_type i) const { return data[i]; } size_type size() const { return limit - data; } // 반복자를 리턴하는 새로운 함수들 iterator begin() { return data; } // 추가됨. 변경가능 const_iterator begin() const { return data; } // 추가됨. 읽기만 가능 iterator end() { return limit; } // 추가됨 const_iterator end() const { return limit; } // 추가됨 private: iterator data; iterator limit; };
11.3 Copy 처리하기 이러한 연산을 정의하지 않으면, 컴파일러가 자동으로 만들어 준다 클래스 제작자는 객체가 생성되고, 복사되고, 대입되고, 소멸될 때 발생하는 사항들을 제어해야 한다.. 지금까지 객체들을 어떻게 생성하는지에 대해서는 설명했다. 이제는 객체들이 복사되고, 대입되고, 소멸될 때 발생하는 일을 어떻게 처리해야 하는지에 대해서 설명.. 이러한 연산을 정의하지 않으면, 컴파일러가 자동으로 만들어 준다 자동으로 만들어진 연산들은 가끔 우리가 원하는 것일 수 있고, 그렇지 않은 경우에는, 직관적이지 않은 방식으로 동작하고 마침내 런타임 오류로 종료하게 될 수도 있다.
복사 생성자 (copy constructor) 함수에 객체를 값으로 (by value) 전달하거나, 함수로부터 객체를 값으로 리턴하는 것은, 그 객체를 복사한다는 의미를 내포. 다른 객체를 초기화 할 때에도 그 객체를 사용해서 명시적으로 복사할 수 있다 vector<int> vi; double d; d = median(vi); // vi를 median의 매개변수로 복사 string line; vector<string> ws=split(line); // split의 리턴값을 ws에 복사 vector<Student_info> vs; vector<Student_info> v2 = vs; // vs를 v2로 복사
복사 생성자 복사 생성자: 클래스 이름과 같은 이름의 멤버 함수 기존의 같은 타입의 객체에 대한 복사본을 통해 새 객체를 초기화 하는 것이 목적이므로, 클래스 자체와 동일한 타입의 단일 인자를 취하게 된다. Vec을 복사할 때, 새로운 공간을 할당하고, 원본으로부터 새로 할당된 목적지로 그 내용을 복사한다. 유틸리티 함수 중에 메모리 할당과 복사를 처리하는 함수가 있으므로, 유틸리티 함수를 이용. create()함수: 한 쌍의 반복자를 취하고, 그 반복자 범위에 해당하는 요소들을 사용하여, 요소들을 생성 Vec<int> vs; Vec<int> v2(vs); template <class T> class Vec { public: Vec(const Vec& v) { create(v .begin(), v.end() ); } // 복사생성자 // 이전과 같음 };
대입 연산자 (assignment operator) 클래스를 정의할 때, 그 클래스의 객체가 복사될 때 어떤 일을 수행할지를 명시하는 것과 같은 방식으로 대입연산자의 수행방식도 명시할 수 있다. 오버로드된 대입 연산자: operator= 클래스의 멤버여야 한다. 리턴 값을 가지므로, 리턴 타입을 정해주어야 한다. 기존 값(왼항)을 제거하고, 새로운 값(오른항)으로 대체 (복사연산자와 다름) 자기-대입(self-assignment)를 고려해야 한다. Vec<int> vs, v2; … v2 = vs; template <class T> class Vec { public: Vec& operator=(const Vec&); // 이전과 같음 }; template <class T> Vec<T>& Vec<T>::operator=(const Vec& rhs) { //자기 대입인지를 확인 if( &rhs != this) { uncreate(); // 왼항의 메모리를 해제 // 오른항에서 왼항으로 요소들을 복사 create(rhs.begin(), rhs(end)); } return *this; };
몇 가지 새로운 개념들 … 클래스 헤더 바깥에서 템플릿 멤버 함수 정의 문법 this 를 사용 템플릿 매개변수 사용: 클래스가 템플릿임을 컴파일러에게 알림 리턴 타입: Vec& 이 아니라, Vec<T>& 템플릿의 스코프 안에서는 생략 가능. <T>가 암묵적으로 사용됨 함수 이름: Vec::operator= 이 아니라, Vec<T>::operator= 현재 정의하는 것이 Vec<T>의 멤버라는 것이 일단 밝혀지면, 더 이상 템플릿 한정자를 반복할 필요가 없다. 따라서, 인자는 const Vec& (물론, const Vec<T>& 로 해도 됨) this 를 사용 멤버 함수 안에서만 유효하다. this는 그 멤버함수가 수행되고 있는 객체에 대한 포인터를 지칭한다. Vec::operator= 내부에서, this의 타입은 Vec* 이다. 여기서, this는 왼항을 가리키는 Vec 객체에 대한 포인터 자기-대입을 처리하지 않으면, 왼항 피연산자의 배열을 uncreate하므로, 요소들이 소멸된다. 그 후, create를 사용해 복사를 시도하므로, 큰 문제가 발생한다. Vec<int> vs, v2; … v2 = vs; // this는 v2객체에 대한 포인터
대입은 초기화가 아니다 초기화 =와 대입(operator =)의 차이점 =기호를 변수의 초기값에 사용하면, 복사생성자가 호출 =기호를 대입구문에 사용하면, operator=를 호출한다 초기화 =와 대입(operator =)의 차이점 대입은 항상 이전 값을 제거하지만, 초기화는 그렇지 않다. 생성자는 항상 초기화를 수행, operator= 멤버함수는 항상 대입을 수행 초기화 상황: (1) 변수 선언할 때 (2) 함수 매개변수에 대해 함수 진입 시 (3) 함수의 리턴 값에서 함수로부터 리턴 시 (4) 생성자 초기 설정자(initializer)에서 string url_ch="~;/?:@=&$-_.+!*'(),"; // 초기화 string spaces(url_ch.size(), ‘ ’); // 초기화. 생성자 인자를 직접 전달 string y; // 초기화. 디폴트 생성자를 호출. y = url_ch; // 대입 vector<string> split(const string &); // 함수 선언 vector<string> v; // 초기화 v = split(line); // 함수 진입시, split의 매개변수를 line으로부터 초기화, // 함수 종료시, 리턴값을 초기화, // v에 대입연산을 수행
소멸자(Destructor) Vec 객체가 소멸될 때, 어떤 일을 해주어야 하는가를 정의 소멸자 객체 소멸 시점 지역 영역(local scope)안에서 생성된 객체는 스코프를 벗어날 때 소멸 동적으로 할당된 객체는 그 객체에 대해 delete할 때 소멸된다 소멸자 소멸자의 이름: ~클래스이름 소멸자는 인자를 갖지 않는다 리턴 값도 없다. 객체가 소멸될 때, 수행되는 정리작업에 해당 보통, 생성자가 할당한 메모리 등의 리소스를 해제하는 작업이 포함 template <class T> class Vec { public: ~Vec() { uncreate(); } // 이전과 같음 };
세 법칙(rule of three) 이러한 연산들이 정의되었다면, T::T() // 하나 또는 그 이상의 생성자, 인자를 가질 수도 있음 T::~T() // 소멸자 T::T(const T&) // 복사 생성자 T::operator=(const T&) // 대입 연산자 이러한 연산들이 정의되었다면, 컴파일러는 각 타입의 객체가 생성되고, 복사되고, 대입되고 소멸될 때마다 그것들을 사용한다. 묵시적으로든, 명시적으로든, 컴파일러는 적합한 연산을 호출한다. 복사 생성자, 소멸자 및 대입연산자는 밀접하게 연관되어 있기 때문에, 그들간의 관계는 세 법칙이라고 알려져 있다.
11.4 동적인 Vec 배열 유지 관리 표준 vector가 지원하는 중요한 기능 중에, push_back 연산을 추가해보자. push_back 함수를 추가하면, Vec의 크기가 증가되어야 한다. Vec 보다 하나 더 많은 요소들을 담을 수 있도록 새로운 공간을 할당 push_back을 많이 호출하는 경우에는 비효율적이다. 효율적인 방법은, 필요한 것 보다 많은 공간을 확보하는 방법 여기서는, 단순하게 하기 위해 두 배 많은 공간을 확보 배열 유지 관리 3개 포인터: 시작, 끝, 할당된 마지막 요소 다음의 포인터 초기화된 요소들 data avail Vec 초기화되지 않은 공간 limit
push_back 함수 putsh_back 함수가 할 일은 메모리 관리 함수 grow(), unchecked_append()에에 맡긴다. template <class T> class Vec { public: size_type size() const { return avail - data; } // changed iterator end() { return avail; } // changed const_iterator end() const { return avail; } // changed void push_back(const T& t) { if (avail == limit) // 필요하면 공간을 확보 grow(); unchecked_append(t); // 새로운 요소를 추가 } void clear() { uncreate(); } bool empty() const { return data == avail; } private: iterator data; // first element in the `Vec' iterator avail; // (one past) the last element in the `Vec' iterator limit; // (one past) the allocated memory
11.5 융통성 있는 메모리 관리 메모리를 할당하고 관리하는 작업은 라이브러리에게 맡기는 것이 좋다. Vec 클래스를 작성할 때, new 및 delete 연산을 이용해서 메모리를 관리하지 않는다. 그 이유는 표준 vector보다 더 제한적이기 때문. 메모리를 할당하고 관리하는 작업은 라이브러리에게 맡기는 것이 좋다. 라이브러리에는 통일된 메모리관리자 인터페이스가 있기 때문에, 라이브러리의 인터페이스는 일관적이다. <memory> 헤더는 allocator<T>라는 클래스를 제공한다 T 타입의 객체를 담을 초기화되지 않은 메모리 블록을 할당 그 메모리의 첫 요소에 대한 포인터를 리턴한다 또한, 메모리 상에 객체를 생성하고, 다시 없애는 기능도 제공한다. 메모리 자체를 해제하지는 않는다. 어떤 공간에 생성된 객체를 담고, 어떤 공간에 초기화하지 않은 객체를 담을지는 프로그래머에게 달려있다.
allocator 클래스 allocator 클래스의 멤버 함수 template <class T> class allocator { public: T* allocate(size_t); void deallocate(T*, size_t); void construct(T*, T); void destroy(T*); // … }; allocator 클래스의 멤버 함수 allocate 멤버: 요청받은 개수 만큼의 요소들을 담을 수 있는 공간을 할당한다. 초기화되지는 않지만, 타입 지정은 되어 있다 (T타입 저장). T* 타입의 포인터를 사용하여 그 주소를 나타낸다 deallocate 멤버: allocate에 의해 할당된 공간의 포인터와 사이즈를 인자로 받아, 공간을 해제시킨다. construct 멤버: allocate에 의해 할당된 공간에 대한 포인터와 그 공간에 복사할 값을 인자로 하여, 하나의 객체를 생성한다. destroy 멤버: T의 소멸자를 호출하여, T 타입의 객체를 소멸시킨다.
allocator 클래스와 관련된 비 멤버 함수 allocate로 할당된 공간에 새로운 요소들을 생성하고 초기화함 In은 입력-반복자 타입, For는 전형적인 포인터로 순방향-반복자 타입 uninitialized_copy 함수: 처음 두 인자로 지정된 시퀀스로부터, 세 번째 인자가 가리키는 목표 시퀀스로 값들을 복사한다. 라이브러리 함수인 copy함수와 비슷하게 동작 uninitialized_fill 함수: 처음 두 인자가 가리키는 공간을 채울 수 있을 만큼의 세 번째 인자의 복사본을 생성한다. 정확한 allocator의 타입을 얻기위해서는 Vec<T>에 allocator<T> 멤버를 추가하여 T타입의 객체를 어떻게 할당할지 알 수 있도록 해야 한다. template <class In, class For> For uninitialized_copy(In, In, For); template <class For, class T> void uninitialized_fill( For, For, const T& );
최종 Vec 클래스 template <class T> class Vec { public: typedef T* iterator; typedef const T* const_iterator; typedef size_t size_type; typedef T value_type; Vec() { create(); } explicit Vec(size_type n, const T& t = T()){ create(n, t); } Vec(const Vec& v) { create(v.begin(), v.end()); } Vec& operator=(const Vec&); // as defined in 11.3.2 ~Vec() { uncreate(); } T& operator[](size_type i) { return data[i]; } const T& operator[](size_type i) const { return data[i]; }
void push_back(const T& t) { if (avail == limit) grow(); unchecked_append(t); } size_type size() const { return avail - data;} //changed iterator begin() { return data; } const_iterator begin() const { return data; } iterator end() { return avail; } //changed const_iterator end() const { return avail; } //changed void clear() { uncreate(); } bool empty() const { return data == avail; }
std::allocator<T> alloc; // 메모리 할당을 처리할 객체 // 배열을 할당 및 초기화 private: iterator data; // first element in the Vec iterator avail; //(one past) the last element in the Vec iterator limit; // (one past) the allocated memory // 메모리 할당 기능을 위해 std::allocator<T> alloc; // 메모리 할당을 처리할 객체 // 배열을 할당 및 초기화 void create(); void create(size_type, const T&); void create(const_iterator, const_iterator); // 배열의 요소들을 소멸시키고, 메모리를 해제시킨다 void uncreate(); // push_back을 지원하는 함수들 void grow(); void unchecked_append(const T&); };
메모리 할당을 처리하는 private 멤버를 구현 create 함수들 메모리를 할당하고, 그 메모리의 요소들을 초기화하고, 적당한 값으로 포인터를 설정하는 일을 한다. 할당된 메모리가 어떤 것이든 상관없이 초기화시키기 때문에, create를 실행한 다음에는 limit 및 avail 포인터들이 항상 동일함 (1) 인자를 갖지 않는 create 함수 빈 벡터를 생성하므로, 포인터가 0 값이 된다. template <class T> void Vec<T>::create() { data = avail = limit = 0; }
(2) 사이즈와 값을 인자로 취하는 create 함수 사이즈 n 만큼의 메모리를 할당하고, data부터 limit 앞까지 val 값으로 초기화 한다. template <class T> void Vec<T>::create(size_type n, const T& val) { data = alloc.allocate(n); // n개의 객체를 저장할 메모리 할당. // 첫 요소에 대한 포인터를 data에 저장 limit = avail = data + n; // limit과 avail을 마지막 요소 다음으로 설정 std::uninitialized_fill(data, limit, val); // 처음 두 인자로 지정된 시퀀스에 세 번째 인자를 복사 }
(3) 한 쌍의 반복자를 인자로 하는 create 함수 j-i개의 메모리를 할당하고, 반복자가 가리키는 i부터 j앞까지의 시퀀스를 할당된 공간의 시작포인터인 data부터 복사 생성한다 template <class T> void Vec<T>::create(const_iterator i, const_iterator j) { data = alloc.allocate(j - i); // j – i개의 객체를 저장할 메모리 할당. // 첫 요소에 대한 포인터를 data에 저장 limit = avail = std::uninitialized_copy(i, j, data); // 반복자 i부터 j까지의 시퀀스를 // data가 가리키는 목표 시퀀스에 복사한다. } template <class T> class Vec { public: Vec(const Vec& v) { create(v .begin(), v.end() ); } // 복사생성자 // 이전과 같음 };
메모리 해제를 처리하는 private 멤버 구현 uncreate() 멤버: 요소들에 대한 소멸자를 호출하고, Vec가 사용한 공간을 리턴한다 create 멤버의 작업을 다시 되돌린다. template <class T> void Vec<T>::uncreate() { if (data) { // data가 0이면, 아무것도 수행하지 않는다 // 생성된 요소들을 (역순으로) 소멸시킨다 iterator it = avail; while (it != data) alloc.destroy(--it); // 할당된 모든 공간을 반환한다 alloc.deallocate(data, limit - data); } // 포인터를 다시 설정하여, Vec을 다시 빈 상태로 만든다 data = limit = avail = 0;
push_back이 사용하는 private 멤버들 구현 grow() 멤버: 다른 요소를 담을 수 있을 만한 여유공간을 할당 push_back을 계속 호출하더라도 빈번한 메모리 할당의 부담을 피할 수 있다 template <class T> void Vec<T>::grow() { // 현재 사용하는 공간의 두 배를 할당하여 크기를 늘린다 size_type new_size = max(2 * (limit - data), ptrdiff_t(1)); // 새 공간을 할당하고, 그곳에 기존의 요소를 복사한다 iterator new_data = alloc.allocate(new_size); iterator new_avail = std::uninitialized_copy(data, avail, new_data); // 예전 공간을 반환한다 uncreate(); // 새로 할당된 공간을 가리키는 포인터를 설정한다 data = new_data; avail = new_avail; limit = data + new_size; }
unchecked_append() 멤버: 생성된 요소들 바로 다음에 하나의 요소를 생성한다 unchecked_append()는 grow() 다음에 바로 호출하기 때문에, 안전한 함수 호출이다. // avail은 할당되었지만, 초기화되지 않은 공간을 가리킨다고 가정 template <class T> void Vec<T>::unchecked_append(const T& val) { alloc.construct(avail++, val); }