Итак, динамическое выделение памяти сопряжено с немалой ответственностью, и я не понимаю, зачем брать на себя лишние обязательства. При использовании vector
и string
необходимость в динамическом выделении памяти возникает значительно реже.
Каждый раз, когда вы готовы прибегнуть к динамическому выделению памяти под массив (то есть собираетесь включить в программу строку вида «new T[…]
»), подумайте, нельзя ли вместо этого воспользоваться vector
или string
. Как правило, string
используется в том случае, если T
является символьным типом, а vector
— во всех остальных случаях. Впрочем, позднее мы рассмотрим ситуацию, когда выбор vector
выгладит вполне разумно. Контейнеры 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
(а wstring
— для basic_string
), поэтому искать следует в шаблоне basic_string
. Вероятно, проще всего обратиться к копирующему конструктору класса. Посмотрите, увеличивает ли он переменную, которая может оказаться счетчиком ссылок. Если такая переменная будет найдена, string
использует подсчет ссылок, а если нет — не использует… или вы просто ошиблись при поиске.