, пока размеры данных не потребуют иного выбора (см. рекомендацию 7), либо пока не станет существенной обеспечение строгой гарантии безопасности при возможной генерации исключений копирующим конструктором или копирующим оператором присваивания типа объектов, хранящихся в контейнере. В последнем случае может оказаться важным, что контейнер
list
обеспечивает строгую гарантию безопасности для операции вставки в коллекцию таких типов.
Избегайте реализации абстракция массива посредством массивов в стиле С, арифметики указателей и примитивов управления памятью. Использование
vector
или
string
не только сделает проще вашу жизнь, но и позволит написать более безопасную и масштабируемую программу.
Обсуждение
Сегодня едва ли не основными вопросами при написании программного обеспечения являются вопросы безопасности и переполнения буфера. Ограничения, связанные с фиксированным размером массивов, доставляют немало беспокойства, даже если приложению удается остаться в рамках корректности. Все эти проблемы связаны с использованием старых средств С, таких как встроенные массивы, указатели и их арифметика, а также управление памятью вручную вместо таких высокоуровневых концепций, как буферы, векторы и строки.
Вот некоторые из причин, по которым массивам в стиле С следует предпочесть стандартные средства С++.
• Они автоматически управляют собственной памятью. Больше не требуется никаких фиксированных буферов с размером "большим, чем любая разумная длина" ("бомба с часовым механизмом" — вот как правильно прочесть это определение), или сплошных перераспределений памяти при помощи
reallос
и соответствующих обновлений указателей.
• У них богатый интерфейс. Вы легко и выразительно можете реализовать сложную функциональность.
• Они совместимы с моделью памяти в С.
vector
и
string::c_str
могут быть переданы функциям API на языке С. В частности, в С++
vector
служит "переходником" к С и другим языкам программирования (см. рекомендации 76 и 78).
• Они обеспечивают расширенные возможности проверки. Стандартные средства позволяют реализовать (в отладочном режиме) итераторы и операторы индексирования, которые способны выявить большой класс ошибок памяти. Многие из современных реализаций стандартной библиотеки предоставляют такие отладочные возможности — воспользуйтесь ими! (См. рекомендацию 83.)
• Они практически не снижают эффективность ваших программ. В классах
vector
и
string
при выборе между эффективностью и безопасностью решение принимается в пользу эффективности (естественно, не в отладочном режиме). Тем не менее, стандартные средства оказываются гораздо лучшей платформой для создания безопасных компонентов, чем обычные массивы и указатели.
• Они стимулируют оптимизацию. Современные реализации стандартной библиотеки включают оптимизации, о которых многие из нас просто никогда бы и не подумали.
Применение массива может быть оправданно, когда его размер фиксирован на стадии компиляции (например,
float[3]
для трехмерной точки; переход к четвертому измерению все равно потребует перепроектирования программы).
служат шлюзом для сообщения с API на других языках. Однако не полагайтесь на то, что итераторы являются указателями; для получения адреса элемента, на который ссылается
vector<T>::iterator iter
, используйте выражение
&*iter
.
Обсуждение
vector
(в первую очередь) и
string::c_str
и
string::data
(во вторую) представляют собой наилучшие способы обмена данными с API на других языках вообще и с библиотеками на С в частности.
Данные
vector
всегда хранятся последовательно, так что получение адреса первого элемента вектора дает указатель на его содержимое. Используйте
&*v.begin
,
&v[0]
или
&v.front
для получения указателя на первый элемент
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>
, который может непосредственно сообщаться с такими функциями.