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 единственный имеет
