|
Программирование >> Включение нужных заголовков
Контейнеры vector и string Все контейнеры STL по-своему полезны, однако большинство программистов C+-I-работает с vector и stri ng чаще, чем с их собратьями, и это вполне понятно. Ведь контейнеры vector и string разрабатывались как замена массивов, а массивы настолько полезны и удобны, что встречаются во всех коммерческих языках программирования от COBOL до Java. В этой главе контейнеры vector и string рассматриваются с нескольких точек зрения. Сначала мы разберемся, чем они превосходят классические массивы STL, затем рассмотрим пути повышения быстродействия vector и string, познакомимся с различными вариантами реализации string, изучим способы передачи string и vector функциям API, принимающим данные в формате С. Далее будет показано, как избежать лишних операций выделения памяти. Глава завершается анализом поучительной аномалии, vector<bool>. Совет 13. Используйте vector и string вместо динамических массивов Принимая решение о динамическом выделении памяти оператором new, вы берете на себя ряд обязательств. 1. Выделенная память в дальнейшем должна быть освобождена оператором delete. Вызов new без последующего delete приводит к утечке ресурсов. 2. Освобождение должно выполняться соответствующей формой оператора delete. Одиночный объект освобождается простым вызовом delete, а для массивов требуется форма delete [ ]. Ошибка в выборе формы delete приводит к непредсказуемым последствиям. На одних платформах программа зависает во время выполнения, а на других она продолжает работать с ошибками, приводящими к утечке ресурсов и порче содержимого памяти. 3. Оператор del ete для освобождаемого объекта должен вызываться ровно один раз. Повторное освобождение памяти также приводит к непредсказуемым последствиям. Итак, динамическое выделение памяти сопряжено с немалой ответственностью, и я не понимаю, зачем брать на себя лишние обязательства. При использовании vector и string необходимость в динамическом выделении памяти возникает значительно реже. Каждый раз, когда вы готовы прибегнуть к динамическому выделению памяти под массив (то есть собираетесь включить в программу строку вида new ![...] ), подумайте, нельзя ли вместо этого воспользоваться vector или stri ng. Как правило, stri ng используется в том случае, если Т является символьным типом, а vector - во всех остальных случаях. Впрочем, позднее мы рассмотрим ситуацию, когда выбор vector<char> выглядит вполне разумно. Контейнеры vector и string избавляют программиста от хлопот, о которых говорилось выше, поскольку они самостоятельно управляют своей памятью. Занимаемая ими память расширяется по мере добавления новых элементов, а при уничтожении vector или string деструктор автоматически уничтожает элементы контейнера и освобождает память, в которой они находятся. Кроме того, vector и string входят в семейство последовательных контейнеров STL, поэтому в вашем распоряжении оказывается весь арсенал алгоритмов STL, работающих с этими контейнерами. Впрочем, алгоритмы STL могут использоваться и с массивами, однако у массивов отсутствуют удобные функции begin, end, size и т. п., а также вложенные определения типов (iterator, reversei terator, val uetype и т. д.), а указатели char* вряд ли могут сравниться со специализированными функциями контейнера string. Чем больше работаешь с STL, тем меньше энтузиазма вызывают встроенные массивы. Если вас беспокоит судьба унаследованного кода, работающего с массивами, не волнуйтесь и смело используйте vector и string. В совете 16 показано, как легко организовать передачу содержимого vector и stri ng функциям С, работающим с массивами, поэтому интеграция с унаследованным кодом обычно обходится без затруднений. Честно говоря, мне приходит в голову лишь одна возможная проблема при замене динамических массивов контейнерами vector/string, причем она относится только к string. Многие реализации string основаны на подсчете ссылок (совет 15), что позволяет избавиться от лишних выделений памяти и копирования символов, а также во многих случаях ускоряет работу контейнера. Оптимизация string на основе подсчета ссылок была сочтена настолько важной, что Комитет по стандартизации С-ы- специально разрешил ее использование. Впрочем, оптимизация нередко оборачивается пессимизацией . При использовании string с подсчетом ссылок в многопоточной среде время, сэкономленное на вьвделении памяти и копировании, может оказаться ничтожно малым по сравнению со временем, затраченным на синхронизацию доступа (за подробностями обращайтесь к статье Саттера Optimizations That Arent (In a Multithreaded World) [20]). Таким образом, при использовании string с подсчетом ссылок в многопоточной среде желательно следить за проблемами быстродействия, обусловленными поддержкой потоковой безопасности. Чтобы узнать, используется ли подсчет ссылок в вашей реализации stri ng, проще всего обратиться к документации библиотеки. Поскольку подсчет ссылок считается оптимизацией, разработчики обычно отмечают его среди положительных особенностей библиотеки. Также можно обратиться к исходным текстам реализации string. Обычно я не рекомендую искать нужную информацию таким способом, но иногда другого выхода просто не остается. Если вы пойдете по этому пути, не забывайте, что string является определением типа для basic string<char> (а wstring - для basic string<wchar t>), поэтому искать следует в шаблоне basic string. Вероятно, проще всего обратиться к копирующему конструктору класса. Посмотрите, увеличивает ли он переменную, которая может оказаться счетчиком ссылок. Если такая переменная будет найдена, string использует подсчет ссылок, а если нет - не использует... или вы просто ошиблись при поиске. Если доступная реализация string построена на подсчете ссылок, а ее использование в многопоточной среде порождает проблемы с быстродействием, возможны по крайней мере три разумных варианта, ни один из которых не связан с отказом от STL. Во-первых, проверьте, не позволяет ли реализация библиотеки отключить подсчет ссылок (обычно это делается изменением значения препроцессорной переменной). Конечно, переносимость при этом теряется, но с учетом минимального объема работы данный вариант все же стоит рассмотреть. Во-вторых, найдите или создайте альтернативную реализацию string (хотя бы частичную), не использующую подсчета ссылок. В-третьих, посмотрите, нельзя ли использовать vector<char> вместо stri ng. Реализации vector не могут использовать подсчет ссылок, поэтому скрытые проблемы многопоточного быстродействия им не присущи. Конечно, при переходе к vector<char> теряются многие удобные функции контейнера string, но большая часть их функциональности доступна через алгоритмы STL, поэтому речь идет не столько о сужении возможностей, сколько о смене синтаксиса. Из всего сказанного можно сделать простой вывод - массивы с динамическим выделением памяти часто требуют лишней работы. Чтобы упростить себе жизнь, используйте vector и string. Совет 14. Используйте reserve для предотвращения лишних операций перераспределения памяти Одной из самых замечательных особенностей контейнеров STL является автоматическое наращивание памяти в соответствии с объемом внесенных данных (при условии, что при этом не превышается максимальный размер контейнера - его можно узнать при помощи функции max size). Для контейнеров vector и string дополнительная память выделяется аналогом функции real!ос. Процедура состоит из четырех этапов: 1. Выделение нового блока памяти, размер которого кратен текущей емкости контейнера. В большинстве реализаций vector и string используется двукратное увеличение, то есть при каждом выделении дополнительной памяти емкость контейнера увеличивается вдвое.
|
© 2006 - 2024 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки. |