Указатели в контейнерах. STL (часть 16)

Оцени эту статью




указатели в контейнерах, STL, программирование для начинающих

Контейнеры STL существенно снижают сложность написания кода, работающего с динамическими структурами данных и, самое главное, повышают его безошибочность. Но в некоторых случаях, при работе с крупными элементами данных в контейнерах, эта техника может порождать неизбежную потерю производительности. Это связано с тем, что начиная с операции помещения элемента в контейнер, и все последующие перемещения элементов могут требовать копирования элементов.

Примечание: Степень выраженности таких эффектов зависит и от типа контейнера, и от рассматриваемых операций. Например, операции взаимного обмена 2-х элементов в ходе сортировки потребует 3-х копирований для контейнера типа vector и не потребует дополнительных копирований для контейнера list. Но операции начального помещения элемента в контейнер (push_back(), insert() и др.) всегда выполняются копированием.

Это может стать проблемой для приложения, когда операции на контейнерах критичны по времени выполнения (или кажутся вам таковыми). Есть и ещё более сложные случаи, когда для класса объектов контейнера не определена операция копирования, или когда объекты представляют собой сами ссылочные объекты, полное копирование для которых необходимо выполнять рекурсивными процедурами следования по всем ссылкам (то, что в языке Python и других называют глубоким копированием).

Всё работает благополучно, как и ранее — сортируем набор записей по произвольным полям и в любом порядке:

16-1

(Обратите внимание, что завершать это приложение нужно по Ctrl+D — End Of File … в Widows, наверное, по Ctrl+Z.)

Но! … Специально были оставлены отладочные «следы» срабатываний конструкторов и деструкторов записей, и записи конструируются 8 раз, а деструктор срабатывает только 4, для локального массива в точке выхода из блока. Локальный массив для нас вообще не представляет интереса. Он введен для упрощения примера только как набор инициализирующих значений. А вот для записей, помещаемых в контейнер, уничтожение записей не происходит, и мы получаем откровенную утечку памяти. Но хуже того, после того как мы удаляем элемент из контейнера (не предпринимая дополнительных действий), мы и не сможем удалить запись, вызвав для неё delete. Это потому, что после вызова erase() мы потеряли к записи единственный путь доступа через итератор (в коде показан цикл с erase(), так наглядней, что эквивалентно clear(), эффект которого, будет тем же самым).

Вывод, который может быть сделан из примера, выглядит так:

  • помещая в контейнеры не объекты, а указатели на них, можно заметно снизить вычислительные затраты на манипуляции с ними (но всегда ли принципиален этот выигрыш при нынешних вычислительных мощностях?).

  • замена объектов на указатели в контейнерах делает код во много раз более опасным в смысле скрытых тонких ошибок, причём такого серьёзного уровня, которые могут дальше приводить к краху приложения.

Здесь некоторую помощь могут оказать умные указатели из последних стандартов C++ (shared_ptr или weak_ptr, но не unique_ptr и не старый добрый и проблемный auto_ptr), нам для этого в предыдущем коде достаточно сменить 4 строчки:

В Windows для shared_ptr необходим #include <memory> , а в других системах не обязательно.

И поведение приложения существенно изменится:

16-2

Но не следует безоглядно обольщаться, так как и умные указатели, снимая одни, порождают другие потенциальные заботы (такие как циклические ссылки и др. о которых достаточно много написано).

Рассылка новых уроков по программированию:

Olej

Об авторе Olej

Стаж практических программных разработок около 40 лет. Преподаватель международной софтверной компании Global Logic. Постоянный автор публикаций IBM Developer Works. Научный редактор книжного издательства компьютерной литературы "Символ-Плюс", Санкт-Петербург.

Добавить комментарий

Код размещайте в тегах: <pre class="lang:c++ decode:true ">YOUR CODE</pre>