Книга: Эффективное использование STL
Совет 13. Используйте vector и string вместо динамических массивов
Совет 13. Используйте vector и string вместо динамических массивов
Принимая решение о динамическом выделении памяти оператором new
, вы берете на себя ряд обязательств.
1. Выделенная память в дальнейшем должна быть освобождена оператором delete
. Вызов new
без последующего delete
приводит к утечке ресурсов.
2. Освобождение должно выполняться соответствующей формой оператора delete
. Одиночный объект освобождается простым вызовом delete
, а для массивов требуется форма delete[]
. Ошибка в выборе формы delete
приводит к непредсказуемым последствиям. На одних платформах программа «зависает» во время выполнения, а на других она продолжает работать с ошибками, приводящими к утечке ресурсов и порче содержимого памяти.
3. Оператор delete
для освобождаемого объекта должен вызываться ровно один раз. Повторное освобождение памяти также приводит к непредсказуемым последствиям.
Итак, динамическое выделение памяти сопряжено с немалой ответственностью, и я не понимаю, зачем брать на себя лишние обязательства. При использовании vector
и string
необходимость в динамическом выделении памяти возникает значительно реже.
Каждый раз, когда вы готовы прибегнуть к динамическому выделению памяти под массив (то есть собираетесь включить в программу строку вида «new T[…]
»), подумайте, нельзя ли вместо этого воспользоваться vector
или string
. Как правило, string
используется в том случае, если T
является символьным типом, а vector
— во всех остальных случаях. Впрочем, позднее мы рассмотрим ситуацию, когда выбор vector<char>
выгладит вполне разумно. Контейнеры vector
и string
избавляют программиста от хлопот, о которых говорилось выше, поскольку они самостоятельно управляют своей памятью. Занимаемая ими память расширяется по мере добавления новых элементов, а при уничтожении vector
или string
деструктор автоматически уничтожает элементы контейнера и освобождает память, в которой они находятся.
Кроме того, vector
и string
входят в семейство последовательных контейнеров STL, поэтому в вашем распоряжении оказывается весь арсенал алгоритмов STL, работающих с этими контейнерами. Впрочем, алгоритмы STL могут использоваться и с массивами, однако у массивов отсутствуют удобные функции begin
, end
, size
и т. п., а также вложенные определения типов (iterator, reverse_iterator, value_type
и т. д.), а указатели char*
вряд ли могут сравниться со специализированными функциями контейнера string
. Чем больше работаешь с STL, тем меньше энтузиазма вызывают встроенные массивы.
Если вас беспокоит судьба унаследованного кода, работающего с массивами, не волнуйтесь и смело используйте vector
и string
. В совете 16 показано, как легко организовать передачу содержимого vector
и string
функциям C, работающим с массивами, поэтому интеграция с унаследованным кодом обычно обходится без затруднений.
Честно говоря, мне приходит в голову лишь одна возможная проблема при замене динамических массивов контейнерами vector/string
, причем она относится только к string
. Многие реализации string
основаны на подсчете ссылок (совет 15), что позволяет избавиться от лишних выделений памяти и копирования символов, а также во многих случаях ускоряет работу контейнера. Оптимизация string
на основе подсчета ссылок была сочтена настолько важной, что Комитет по стандартизации C++ специально разрешил ее использование.
Впрочем, оптимизация нередко оборачивается «пессимизацией». При использовании string
с подсчетом ссылок в многопоточной среде время, сэкономленное на выделении памяти и копировании, может оказаться ничтожно малым по сравнению со временем, затраченным на синхронизацию доступа (за подробностями обращайтесь к статье Саттера «Optimizations That Aren't (In a Multithreaded World)» [20]). Таким образом, при использовании string
с подсчетом ссылок в многопоточной среде желательно следить за проблемами быстродействия, обусловленными поддержкой потоковой безопасности.
Чтобы узнать, используется ли подсчет ссылок в вашей реализации string
, проще всего обратиться к документации библиотеки. Поскольку подсчет ссылок считается оптимизацией, разработчики обычно отмечают его среди положительных особенностей библиотеки. Также можно обратиться к исходным текстам реализации string
. Обычно я не рекомендую искать нужную информацию таким способом, но иногда другого выхода просто не остается. Если вы пойдете по этому пути, не забывайте, что string
является определением типа для basic_string<char>
(а wstring
— для basic_string<wchar_t>
), поэтому искать следует в шаблоне basic_string
. Вероятно, проще всего обратиться к копирующему конструктору класса. Посмотрите, увеличивает ли он переменную, которая может оказаться счетчиком ссылок. Если такая переменная будет найдена, string
использует подсчет ссылок, а если нет — не использует… или вы просто ошиблись при поиске.
Если доступная реализация string
построена на подсчете ссылок, а ее использование в многопоточной среде порождает проблемы с быстродействием, возможны по крайней мере три разумных варианта, ни один из которых не связан с отказом от STL. Во-первых, проверьте, не позволяет ли реализация библиотеки отключить подсчет ссылок (обычно это делается изменением значения препроцессорной переменной). Конечно, переносимость при этом теряется, но с учетом минимального объема работы данный вариант все же стоит рассмотреть. Во-вторых, найдите или создайте альтернативную реализацию string
(хотя бы частичную), не использующую подсчета ссылок. В-третьих, посмотрите, нельзя ли использовать vector<char>
вместо string
. Реализации vector
не могут использовать подсчет ссылок, поэтому скрытые проблемы многопоточного быстродействия им не присущи. Конечно, при переходе к vector<char>
теряются многие удобные функции контейнера string
, но большая часть их функциональности доступна через алгоритмы STL, поэтому речь идет не столько о сужении возможностей, сколько о смене синтаксиса.
Из всего сказанного можно сделать простой вывод — массивы с динамическим выделением памяти часто требуют лишней работы. Чтобы упростить себе жизнь, используйте vector
и string
.
- Совет 13. Используйте vector и string вместо динамических массивов
- Совет 14. Используйте reserve для предотвращения лишних операций перераспределения памяти
- Совет 15. Помните о различиях в реализации string
- Совет 16. Научитесь передавать данные vector и string функциям унаследованного интерфейса
- Совет 17. Используйте «фокус с перестановкой» для уменьшения емкости
- Совет 18. Избегайте vector
- Контейнеры vector и string
- 8.1.4. Сравнение массивов
- Using Double Quotes to Resolve Variables in Strings with Embedded Spaces
- 8.1.22. Чередование массивов
- Используйте аутсорсинг
- Фокус-группы вместо пудры
- История развития компьютеров (вместо пролога)
- Текстовые форматы и кодировки, или Почему иногда вместо текста я вижу абракадабру?
- На сайтах вместо текста отображается непонятный набор символов
- Материнская плата имеет возможность организации RAID-массивов из двух SATA-дисков. Можно ли подключить к ней только один...
- Можно ли указать использование по умолчанию вместо C:Program Files другого каталога для установки программ?
- Что делать, если вместо русских букв в программах – непонятные символы?