intVec.at(300) = 2;
} catch(std::out_of_range& e) {
std::cerr << 'out_of_range: ' << e.what() << std::endl;
}
Как вы знаете, если обратиться к элементу за пределами массива с помощью operator[]
, оператор сделает то, что ему сказано сделать, и вернет то, что находится в указанной области памяти. Это плохо, так как либо программа обрушится в результате попытки доступа к области памяти, к которой она доступа не имеет, либо она молча изменит содержимое области памяти, принадлежащей другому объекту кучи, что обычно еще хуже. operator[]
для vector работает точно так же, но когда требуется обезопасить код, используйте at
.
Итак, вот краткий курс по vector
. Но что vector
? Если вы используете С++, то вас, вероятно, волнуют проблемы производительности, и вам не понравится, если вам просто дадут что-то и скажут, что это работает. Вполне справедливо. За обсуждением работы vector
и советами по его эффективному использованию обратитесь к рецепту 6.2.
Рецепт 6.2.
6.2. Эффективное использование vector
Вы используете vector
, и при этом имеются жесткие требования по объему или времени выполнения кода и требуется снизить или устранить все накладные расходы.
Поймите, как реализован vector
, узнайте о сложности методов вставки и удаления и минимизируйте ненужные операции с памятью с помощью метода reserve
. Пример 6.2 показывает некоторые из этих методик в действии.
#include <iostream>
#include <vector>
#include <string>
using std::vector;
using std::string;
void f(vector<string>& vec) {
// Передача vec по ссылке (или,
// если требуется, через указатель)
// ...
}
int main() {
vector<string> vec(500); // При создании vector говорим, что в него
// планируется поместить определенное количество
// объектов
vector<string> vec2;
// Заполняем vec...
f(vec);
vec2 reserve(500); // Или постфактум говорим vector,
// что требуется буфер достаточно большого
// размера для хранения объектов
// Заполняем vec2...
}
Ключ к эффективному использованию vector
лежит в знании его работы. Когда у вас есть четкое представление реализации vector
, вопросы производительности становятся очевидными.
vector
— это по сути управляемый массив. Более конкретно, vector<T>
— это непрерывный фрагмент памяти (т.е. массив), который достаточно велик для хранения T
, где vector
от массива — это автоматическое управление памятью массива, методы для вставки и получения элементов и методы, которые предоставляют метаданные о контейнере, такие как размер (число элементов) и емкость (размер буфера), а также информацию о типе: vector<T>::value_type
— это тип T
, vector<T>::pointer
— это тип указатель-на-T
и т.д. Два последних и некоторые другие являются частью любого стандартного контейнера, и они позволяют писать обобщенный код, который работает независимо от типа T
. Рисунок 6.1 показывает графическое представление того, что предоставляют некоторые из методов vector
, если vector
имеет размер 7 и емкость 10.

Рис. 6.1. Внутренности vector
Если вам любопытно, как поставщик вашей стандартной библиотеки реализовал vector
, скомпилируйте пример 6.1 и пройдите в отладчике все вызовы методов vector или откройте заголовочный файл <vector>
реализации стандартной библиотеки и изучите его. Код, который вы там увидите, по большей части не является дружественным к читателю, но он должен осветить некоторые моменты. Во-первых, если вы еще не видели кода библиотеки, он даст вам представление о методиках реализации, используемых для написания эффективного, переносимого обобщенного кода. Во-вторых, он даст точное представление о том, что представляют собой используемые вами контейнеры. При написании кода, который должен работать с различными реализациями стандартной библиотеки, это следует сделать в любом случае.
Однако независимо от поставщика библиотеки почти все реализации векторов похожи. В них есть переменная экземпляра, которая указывает на массив из T
, и элементы, добавляемые или присваиваемые вами, с помощью конструктора копирования или операции присвоения помешаются в элементы этого массива.
Обычно добавление объекта T
в следующий доступный слот буфера выполняется с помощью копирующего конструктора и new, которому передается тип создаваемого объекта, а также адрес, по которому он должен быть создан. Если вместо этого явно присвоить значение слоту, используя его индекс (с помощью operator[]
или at
), то будет использован оператор присвоения T
. Заметьте, что в обоих случаях объект клонируется либо с помощью конструктора копирования, либо T::operator=
. vector
не просто хранит адрес добавляемого объекта. Именно по этой причине любой тип, сохраняемый в векторе, должен поддерживать копирующий конструктор и присвоение. Эти свойства означают, что эквивалентный объект типа T
может быть создан с помощью вызова конструктора копирования T
или