if (size()==capacity())
reserve(size() = 0 ? 8 : 2*size()); // убедимся, что
// есть место
// сначала копируем последний элемент в неинициализированную ячейку:
alloc.construct(elem+sz,*back());
++sz;
iterator pp = begin()+index; // место для записи значения val
for (iterator pos = end()–1; pos!=pp; ––pos)
*pos = *(pos–1); // переносим элемент на одну позицию вправо
*(begin()+index) = val; // "insert" val
return pp;
}
Обратите внимание на следующие факты.
• Итератор не может ссылаться на ячейку, находящуюся за пределами последовательности, поэтому мы используем указатели, такие как elem+space
. Это одна из причин, по которым распределители памяти реализованы на основе указателей, а не итераторов.
• Когда мы используем функцию reserve()
, элементы могут быть перенесены в новую область памяти. Следовательно, мы должны запомнить индекс вставленного элемента, а не итератор, установленный на него. Когда элементы вектора перераспределяются в памяти, итераторы, установленные на них, становятся некорректными — их можно интерпретировать как ссылки на старые адреса.
• Наше использование распределителя памяти A
является интуитивным, но не точным. Если вам придется реализовывать контейнер, то следует внимательно изучить стандарт.
• Тонкости, подобные этим, позволяют избежать непосредственной работы с памятью на нижнем уровне. Естественно, стандартный класс vector
, как и остальные стандартные контейнеры, правильно реализует эти важные семантические тонкости. Это одна из причин, по которым мы настоятельно рекомендуем использовать стандартную библиотеку, а не “кустарные” решения.
По причинам, связанным с эффективностью, мы не должны применять функции insert()
и erase()
к среднему элементу вектора, состоящего из 100 тыс. элементов; для этого лучше использовать класс list
(и класс map; см. раздел 21.6). Однако операции insert()
и erase()
можно применять ко всем векторам, а их производительность при перемещении небольшого количества данных является непревзойденной, поскольку современные компьютеры быстро выполняют такое копирование (см. упр. 20). Избегайте (связанных) списков, состоящих из небольшого количества маленьких элементов.
20.9. Адаптация встроенных массивов к библиотеке STL
Мы многократно указывали на недостатки встроенных массивов: они неявно преобразуют указатели при малейшем поводе, их нельзя скопировать с помощью присваивания, они не знают своего размера (см. раздел 20.5.2) и т.д. Кроме того, мы отмечали их преимущества: они превосходно моделируют физическую память.
Для того чтобы использовать преимущества массивов и контейнеров, мы можем создать контейнер типа array, обладающий достоинствами массивов, но не имеющий их недостатков. Вариант класса array
был включен в стандарт как часть технического отчета Комитета по стандартизации языка С++. Поскольку свойства, включенные в этот отчет, не обязательны для реализации во всех компиляторах, класс array
может не содержаться в вашей стандартной библиотеке. Однако его идея проста и полезна.
template
struct array {
typedef T value_type;
typedef T* iterator;
typedef T* const_iterator;
typedef unsigned int size_type; // тип индекса
T elems[N];
// не требуется явное создание/копирование/уничтожение
iterator begin() { return elems; }
const_iterator begin() const { return elems; }
iterator end() { return elems+N; }
const_iterator end() const { return elems+N; }
size_type size() const;
T& operator[](int n) { return elems[n]; }
const T& operator[](int n) const { return elems[n]; }
const T& at(int n) const; // доступ с проверкой диапазона
T& at(int n); // доступ с проверкой диапазона
T * data() { return elems; }
const T * data() const { return elems; }
};