Чтение онлайн

на главную

Жанры

Эффективное использование STL
Шрифт:

Прежде чем переходить к третьей категории затрат, стоит сделать небольшое замечание. То, что написано в предыдущем абзаце — правда, только правда и ничего, кроме правды, но это не вся правда. Интервальная форма

insert
может переместить элемент в конечную позицию за одну операцию только в том случае, если ей удастся определить расстояние между двумя итераторами без перехода. Это возможно почти всегда, поскольку такой возможностью обладают все прямые итераторы, а они встречаются практически повсеместно. Все итераторы стандартных контейнеров обладают функциональными возможностями прямых итераторов — в том числе и итераторы нестандартных хэшированных контейнеров (совет 25). Указатели, играющие роль итераторов в массивах, тоже обладают этой возможностью. В общем-то, из всех стандартных итераторов она не присуща только итераторам
ввода и вывода. Следовательно, все сказанное выше справедливо в том случае, если итераторы, передаваемые интервальной форме
insert
, не являются итераторами ввода (скажем,
istream_iterator
— см. совет 6). Только в этом случае интервальной форме
insert
приходится перемещать элементы на свои итоговые места по одной позиции, вследствие чего преимущества интервальной формы теряются (для итераторов вывода эта проблема вообще не возникает, поскольку итераторы вывода не могут использоваться для определения интервала
insert
).

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

После этого вставляется новый элемент. В совете 14 также говорится о том, что при заполнении всей памяти многие реализации векторов удваивают свою емкость, поэтому вставка

numValues
новых элементов может привести к тому, что новая память будет выделяться со временем log2numValues. В совете 14 упоминается о существовании реализации, обладающей таким поведением, поэтому последовательная вставка 1000 элементов может привести к 10 операциям выделения памяти с побочными затратами на копирование элементов). С другой стороны, интервальная вставка может вычислить объем необходимой памяти еще до начала вставки (если ей передаются прямые итераторы), поэтому ей не придется выделять новую память больше одного раза. Как нетрудно предположить, экономия может оказаться довольно существенной.

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

Из стандартных последовательных контейнеров остается только

list
, но и в этом случае интервальная форма
insert
обладает преимуществами перед одноэлементной. Конечно, такой фактор, как лишние вызовы функций, продолжает действовать, но из-за некоторых особенностей связанных списков проблемы с копированием и выделением памяти отсутствуют. Вместо них возникает другая проблема: многократные избыточные присваивания указателям
next
и
prev
для некоторых узлов списка.

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

next
и
prev
нового узла. Кроме того, необходимо задать указатель
next
предыдущего узла (назовем его узлом B) и указатель
prev
следующего узла (назовем его узлом A).

Предположим, в список была вставлена серия новых узлов вызовами одноэлементной версии

insert
. Во всех узлах, кроме последнего, значение
next
будет задаваться дважды — сначала указатель будет ссылаться на узел А, а затем на следующий вставленный элемент. Указатель
prev
узла А будет изменяться при каждой вставке нового узла в предшествующую позицию. Если перед А в список включаются
numValues
узлов, будет выполнено
numValues-1
лишних присваиваний указателю
next
вставленных узлов и
numValues-1
лишних присваиваний указателю
prev
узла А, то есть в общей сложности
2*(numValues-l)
лишних операций присваивания. Конечно, присваивание указателю обходится недорого, но зачем вообще платить, если можно обойтись без этого?

Наверное, вы уже поняли, что без лишних присваиваний действительно можно обойтись. Для этого достаточно воспользоваться интервальной формой

insert
контейнера
list
. Функция заранее знает, сколько узлов будет вставлено в список, что позволяет сразу присвоить каждому указателю правильное значение.

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

Если отвлечься от соображений эффективности, остается непреложный факт: вызовы интервальных функций более компактны, а программа становится более наглядной, что упрощает ее долгосрочное сопровождение. Даже этих двух причин вполне достаточно для того, чтобы отдать предпочтение интервальным функциям, а выигрыш в эффективности можно рассматривать как бесплатное приложение.

После столь пространных рассуждений о чудесах интервальных функций было бы уместно привести краткую сводку таких функций. Если вы заранее знаете, какие функции контейнеров существуют в интервальных версиях, вам будет проще определить, когда ими можно воспользоваться. В приведенных ниже сигнатурах тип

iterator
в действительности означает тип итератора для данного контейнера, то есть контейнер
::iterator
. С другой стороны, тип
InputIterator
означает любой допустимый итератор ввода.

• Интервальные конструкторы. У всех стандартных контейнеров существуют конструкторы следующего вида:

контейнер::контейнер(InputIterator begin, // Начало интервала

InputIterator end); // Конец интервала

При передаче этому конструктору итераторов

istream_iterator
и
istreambuf_iterator
(совет 29) иногда встречается одна из самых удивительных ошибок C++, вследствие которой компилятор интерпретирует эту конструкцию как объявление функции, а не как определение нового объекта контейнера. В совете 6 рассказано все, что необходимо знать об этой ошибке, в том числе и способы ее преодоления.

• Интервальная вставка. Во всех стандартных последовательных контейнерах присутствует следующая форма insert:

void контейнер::insert(iterator position, // Позиция вставки

InputIterator begin, // Начало интервала

InputIterator end); // Конец интервала

Ассоциативные контейнеры определяют позицию вставки при помощи собственных функций сравнения, поэтому в них предусмотрена сигнатура без параметра

position
:

void контейнер::insert(InputIterator begin, InputIterator end);

Рассматривая возможности замены одноэлементных вызовов insert интервальными версиями, не забывайте, что некоторые одноэлементные варианты маскируются под другими именами. Например, push_front и push_back заносят в контейнер отдельный элемент, хотя в их названии отсутствует слово insert. Если в программе встречается циклический вызов push_front/push_back или алгоритм (например, copy), которому в качестве параметра передается front_inserter или back_inserter, перед вами потенциальный кандидат для применения интервальной формы insert.

Поделиться:
Популярные книги

Набирая силу

Каменистый Артем
2. Альфа-ноль
Фантастика:
фэнтези
боевая фантастика
рпг
6.29
рейтинг книги
Набирая силу

Сердце Дракона. Том 11

Клеванский Кирилл Сергеевич
11. Сердце дракона
Фантастика:
фэнтези
героическая фантастика
боевая фантастика
6.50
рейтинг книги
Сердце Дракона. Том 11

Польская партия

Ланцов Михаил Алексеевич
3. Фрунзе
Фантастика:
попаданцы
альтернативная история
5.25
рейтинг книги
Польская партия

Восьмое правило дворянина

Герда Александр
8. Истинный дворянин
Фантастика:
фэнтези
попаданцы
аниме
5.00
рейтинг книги
Восьмое правило дворянина

Ты предал нашу семью

Рей Полина
2. Предатели
Любовные романы:
современные любовные романы
5.00
рейтинг книги
Ты предал нашу семью

Как я строил магическую империю 2

Зубов Константин
2. Как я строил магическую империю
Фантастика:
попаданцы
аниме
5.00
рейтинг книги
Как я строил магическую империю 2

Наследник старого рода

Шелег Дмитрий Витальевич
1. Живой лёд
Фантастика:
фэнтези
8.19
рейтинг книги
Наследник старого рода

Возвращение Низвергнутого

Михайлов Дем Алексеевич
5. Изгой
Фантастика:
фэнтези
9.40
рейтинг книги
Возвращение Низвергнутого

Лорд Системы

Токсик Саша
1. Лорд Системы
Фантастика:
фэнтези
попаданцы
рпг
4.00
рейтинг книги
Лорд Системы

Довлатов. Сонный лекарь 2

Голд Джон
2. Не вывожу
Фантастика:
альтернативная история
аниме
5.00
рейтинг книги
Довлатов. Сонный лекарь 2

Дурашка в столичной академии

Свободина Виктория
Фантастика:
фэнтези
7.80
рейтинг книги
Дурашка в столичной академии

Барон диктует правила

Ренгач Евгений
4. Закон сильного
Фантастика:
фэнтези
попаданцы
аниме
5.00
рейтинг книги
Барон диктует правила

Кодекс Охотника. Книга XIII

Винокуров Юрий
13. Кодекс Охотника
Фантастика:
боевая фантастика
попаданцы
аниме
7.50
рейтинг книги
Кодекс Охотника. Книга XIII

Удобная жена

Волкова Виктория Борисовна
Любовные романы:
современные любовные романы
5.00
рейтинг книги
Удобная жена