v
. Для получения указателя на n-й элемент вектора лучше сначала провести арифметические вычисления, а затем получить адрес (например, &v.begin()[n]
или &v[n]
) вместо получения указателя на начало данных с последующим применением арифметики указателей (например, (&v.front())[n]
). Это связано с тем, что в первом случае в отладочной реализации выполняется проверка на доступ к элементу за пределами v
(см. рекомендацию 83).
Нельзя полагаться на то, что v.begin()
возвращает указатель на первый элемент или, в общем случае, что итераторы вектора являются указателями. Хотя некоторые реализации STL определяют vector<T>::iterator
как обычный указатель T*
, итераторы могут быть (и все чаще так оно и есть) полноценными типами (еще раз см. рекомендацию 83).
Хотя в большинстве реализаций для string
также используется непрерывный блок памяти, это не гарантируется стандартом, так что никогда не используйте адрес символа в строке, считая его указателем на все содержимое строки. Хорошая новость заключается в том, что функция string::c_str
всегда возвращает строку в стиле С с завершающим нулевым символом (string::data
также возвращает указатель на непрерывный блок памяти, но не гарантирует наличия завершающего нулевого символа).
Когда вы передаете указатель на данные объекта v
типа vector
, код на языке С может как читать, так и записывать элементы v
; однако он не должен выходить за границы данных. Хорошо продуманный API на языке С должен получать наряду с указателем либо максимальное количество объектов (до v.size()
), либо указатель на элемент, следующий за последним (&*v.begin()+v.size()
).
Если у вас есть контейнер объектов типа T
, отличный от vector
или string
, и вы хотите передать его содержимое (или заполнить его) функции API на другом языке программирования, которая ожидает указатель на массив объектов типа T
, скопируйте содержимое контейнера в (или из) vector<T>
, который может непосредственно сообщаться с такими функциями.
79. Храните в контейнерах только значения или интеллектуальные указатели
Храните к контейнерах объекты-значения. Контейнеры полагают, что их содержимое имеет тип значения, включая непосредственно хранящиеся значения, интеллектуальные указатели и итераторы.
Наиболее распространенное использование контейнеров — для непосредственного хранения значений (например, vector<int>, set<string>
). В случае контейнеров указателей, если контейнер владеет объектами, на которые указывает, то лучше использовать контейнер интеллектуальных указателей со счетчиком ссылок (например, list<shared_ptr<Widget> >
); в противном случае можно выбрать контейнер обычных указателей (например, list<Widget*>
) или иных значений, подобных указателям — таких как итераторы (например, list<vector<Widget>::iterator>
).
auto_ptr<T>
не являются объектами-значениями из-за своей семантики передачи владения при копировании. Использование контейнера объектов auto_ptr
(например, vector<auto_ptr<int> >
) должно привести к ошибке компиляции. Но даже в случае успешной компиляции никогда не пишите такой код — вместо этого вам следует использовать контейнер интеллектуальных указателей shared_ptr
.
container<shared_ptr<Base> >
. Альтернативой является хранение прокси-объектов, невиртуальные функции которых передают вызовы соответствующим виртуальным функциям реальных объектов.
DatabaseLock
или TcpConnection
), их следует хранить опосредованно, с использованием интеллектуальных указателей (например, container<shared_ptr<DatabaseLock> >
или container<shared_ptr<TcpConnection> >
).
map<Thing,Widget>
, но некоторые Thing
не имеют связанных с ними объектов Widget
, можно использовать map<Thing, shared_ptr<Widget> >
.
MainContainer::iterator
(которые являются значениями).
80. Предпочитайте push_back
другим способам расширения последовательности
Используйте push_back
везде, где это возможно. Если для вас не важна позиция вставки нового объекта, лучше всего использовать для добавления элемента в последовательность функцию push_back
. Все прочие средства могут оказаться как гораздо менее быстрыми, так и менее понятными.
Вы можете вставить элементы в последовательность в разных точках с использованием insert
; добавить элементы в последовательность можно разными способами, включая следующие:
vector<int> vec; // vec пуст
vec.resize(vec.size() + 1, 1); // vec содержит { 1 }
vec.insert(vec.end(), 2); // vec содержит { 1, 2 }
vec.push_back(3); // vec содержит { 1, 2, 3 }
Среди прочих методов push_back
единственный имеет