{
if (newalloc<=space) return; // размер не уменьшается
T* p = alloc.allocate(newalloc); // выделяем новую память
for (int i=0; i<sz; ++i) alloc.construct(&p[i],elem[i]);
// копируем
for (int i=0; i<sz; ++i) alloc.destroy(&elem[i]); // уничтожаем
alloc.deallocate(elem,space); // освобождаем старую память
elem = p;
space = newalloc;
}
Мы перемещаем элемент в новый участок памяти, создавая копию в неинициализированной памяти, а затем уничтожая оригинал. Здесь нельзя использовать присваивание, потому что для таких типов, как
string
, присваивание подразумевает, что целевая область памяти уже проинициализирована.
Имея функции
reserve()
,
vector<T,A>::push_back()
, можно без труда написать следующий код.
template<class T, class A>
void vector<T,A>::push_back(const T& val)
{
if (space==0) reserve(8); // начинаем с памяти для 8 элементов
else if (sz==space) reserve(2*space); // выделяем больше памяти
alloc.construct(&elem[sz],val); // добавляем в конец
// значение val
++sz; // увеличиваем размер
}
Аналогично можно написать функцию
vector<T,A>::resize()
.
template<class T, class A>
void vector<T,A>::resize(int newsize, T val = T())
{
reserve(newsize);
for (int i=sz; i<newsize; ++i) alloc.construct(&elem[i],val);
// создаем
for (int i = newsize; i<sz; ++i) alloc.destroy(&elem[i]);
// уничтожаем
sz = newsize;
}
Обратите внимание на то, что, поскольку некоторые типы не имеют конструкторов по умолчанию, мы снова предоставили возможность задавать начальное значение для новых элементов.
Другое новшество — деструктор избыточных элементов при уменьшении вектора. Представьте себе деструктор, превращающий объект определенного типа в простой набор ячеек памяти.
“Непринужденное обращение с распределителями памяти” — это довольно сложное и хитроумное искусство. Не старайтесь злоупотреблять им, пока не почувствуете, что стали экспертом.
19.4. Проверка диапазона и исключения
Мы проанализировали текущее состояние нашего класса
vector
и обнаружили (с ужасом?), что в нем не предусмотрена проверка выхода за пределы допустимого диапазона. Реализация оператора
operator[]
не вызывает затруднений.
template<class T, class A> T& vector<T,A>::operator[](int n)
{
return elem[n];
}
Рассмотрим следующий пример:
vector<int> v(100);
v[–200] = v[200]; // Ой!
int i;
cin>>i;
v[i] = 999; // повреждение произвольной ячейки памяти
Этот код компилируется и выполняется, обращаясь к памяти, не принадлежащей нашему объекту класса
vector
. Это может создать большие неприятности! В реальной программе такой код неприемлем. Попробуем улучшить наш класс
vector
, чтобы решить эту проблему. Простейший способ — добавить в класс операцию проверки доступа с именем
at()
.
struct out_of_range { /* ... */ }; // класс, сообщающий об ошибках,
// связанных с выходом за пределы допустимого диапазона
template<class T, class A = allocator<T> > class vector {
// ...
T& at(int n); // доступ с проверкой
const T& at(int n) const; // доступ с проверкой
T& operator[](int n); // доступ без проверки
const T& operator[](int n) const; // доступ без проверки
// ...
};
template<class T, class A > T& vector<T,A>::at(int n)
{
if (n<0 || sz<=n) throw out_of_range();
return elem[n];
}
template<class T, class A > T& vector<T,A>::operator[](int n)
// как прежде
{
return elem[n];
}
Итак, мы можем написать следующую функцию:
void print_some(vector<int>& v)
{
int i = –1;
cin >> i;
while(i!= –1) try {
cout << "v[" << i << "]==" << v.at(i) << "\n";