Поскольку мьютекс контейнера освобождается в деструкторе
Lock
, важно обеспечить
уничтожение
Lock
сразу же после освобождения мьютекса. Для этого мы создаем новый блок, в котором определяется объект
Lock
, и закрываем его, как только надобность в мьютексе отпадает. На первый взгляд кажется, что вызов
releaseMutexFor
попросту заменен необходимостью закрыть блок, но это не совсем так. Если мы забудем создать новый блок для
Lock
, мьютекс все равно будет освобожден, но это может произойти позднее положенного момента — при выходе из внешнего блока. Если забыть о вызове
releaseMutexFor
, мьютекс вообще не освобождается.
Более того, решение, основанное на классе
Lock
, лучше защищено от исключений. C++ гарантирует уничтожение локальных объектов при возникновении исключения, поэтому
Lock
освободит мьютекс, даже если исключение произойдет при использовании объекта
Lock
. При использовании парных вызовов
getMutexFor/releaseMutexFor
мьютекс не будет освобожден, если исключение происходит после вызова
getMutexFor
, но перед вызовом
releaseMutexFor
.
Исключения и управление ресурсами важны, но данный совет посвящен другой теме — потоковой безопасности в STL. Как говорилось выше, вы можете надеятьсяна то, что реализация библиотеки обеспечивает параллельное чтение из одного контейнера и одновременную запись в разные контейнеры. Не надейтесь, что библиотека избавит вас от ручной синхронизации и не рассчитывайте на поддержку многопоточности.
Контейнеры vector и string
Все контейнеры STL по-своему полезны, однако большинство программистов C++ работает с
vector
и string чаще, чем с их собратьями, и это вполне понятно. Ведь контейнеры
vector
и
string
разрабатывались как замена массивов, а массивы настолько полезны и удобны, что встречаются во всех коммерческих языках программирования от COBOL до Java.
В этой главе контейнеры
vector
и
string
рассматриваются с нескольких точек зрения. Сначала мы разберемся, чем они превосходят классические массивы STL, затем рассмотрим пути повышения быстродействия
vector
и
string
, познакомимся с различными вариантами реализации
string
, изучим способы передачи
string
и
vector
функциям API, принимающим данные в формате C. Далее будет показано, как избежать лишних операций выделения памяти. Глава завершается анализом поучительной аномалии,
vector<bool>
.
Совет 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
с подсчетом ссылок в многопоточной среде желательно следить за проблемами быстродействия, обусловленными поддержкой потоковой безопасности.