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

ЖАНРЫ

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

if (i != se.end){ // Устранить

 const_cast<Empioyee&>(*i).setTitle("Corporate Deity"); // константность *i

}

Мы берем объект, на который ссылается

i
, и сообщаем компилятору, что результат должен интерпретироваться как ссылка на (неконстантный) объект
Employee
, после чего вызываем
setTitle
для полученной ссылки. Я не буду тратить время на долгие объяснения и лучше покажу, почему альтернативное решение работает совсем не так, как можно было бы ожидать.

Многие программисты пытаются

воспользоваться следующим кодом:

if (i != se.end){ // Преобразовать *i

 static_cast<Employee>(*i).setTitle("Corporate Deity"); // к Employee

}

Приведенный фрагмент эквивалентен следующему:

if (i != se.end){ // То же самое,

 ((Employee)(*i)).setTitle("Corporate Deity"); // но с использованием

} // синтаксиса С

Оба фрагмента компилируются, но вследствие эквивалентности работают неправильно. На стадии выполнения объект

*i
не модифицируется, поскольку в обоих случаях результатом преобразования является временный анонимный объект — копия
*i
, и
setTitle
вызывается для анонимного объекта, а не для
*i
! Обе синтаксические формы эквивалентны следующему фрагменту:

if (i != se.end) {

 Employee tempCopy(*i); // Скопировать *i в tempCopy

 tempCopy.setTitle("Corporate Deity"); // Изменить tempCopy

}

Становится понятно, почему преобразование должно приводить именно к ссылке — тем самым мы избегаем создания нового объекта. Вместо этого результат преобразования представляет собой ссылку на существующийобъект, на который указывает

i
. При вызове
setTitle
для объекта, обозначенного ссылкой, функция вызывается для
*i
, чего мы и добивались.

Все сказанное хорошо подходит для контейнеров set и multiset, но при переходе к map/multimap ситуация усложняется. Вспомните, что

map<K, V>
и
multimap<K, V>
содержат элементы типа
pair<const K, V>
. Объявление
const
означает, что первый компонент пары определяетсякак константа, а из этого следует, что любые попытки устранить его константность приводят к непредсказуемому результату. Теоретически реализация STL может записывать такие данные в область памяти, доступную только для чтения (например, в страницу виртуальной памяти, которая после исходной записи защищается вызовом системной функции), и попытки устранить их константность в лучшем случае ни к чему не приведут. Я никогда не слышал о реализациях, которые бы поступали подобным образом, но если вы стремитесь придерживаться правил, установленных в Стандарте, — никогдане пытайтесь устранять константность ключей в контейнерах
map
и
multimap
.

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

Многие преобразования (включая только что рассмотренные) не являются абсолютно

необходимыми. Самый безопасный и универсальный способ модификации элементов контейнера
set
,
multiset
,
map
или
multimap
состоит из пяти простых шагов.

1. Найдите элемент, который требуется изменить. Если вы не уверены в том, как сделать это оптимальным образом, обратитесь к рекомендациям по поводу поиска в совете 45.

2. Создайте копию изменяемого элемента. Помните, что для контейнеров

map/multimap
первый компонент копии не должен объявляться константным — ведь именно его мы и собираемся изменить!

3. Удалите элемент из контейнера. Обычно для этого используется функция

erase
(см. совет 9).

4. Измените копию и присвойте значение, которое должно находиться в контейнере.

5. Вставьте новое значение в контейнер. Если новый элемент в порадке сортировки контейнера находится в позиции удаленного элемента или в соседней позиции, воспользуйтесь «рекомендательной» формой

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

EmpIDSet se; // Контейнер set объектов Employee, упорядоченных по коду

Employee SelectedID; // Объект работника с заданным кодом

EmpIDSet::iterator i = // Этап 1: поиск изменяемого элемента

 se.find(selectedID);

if (i != se.end) {

 Employee e(*i); //Этап 2: копирование элемента

 se.erase(i++); // Этап 3: удаление элемента.

// Увеличение итератора

// сохраняет его

// действительным (см. совет 9)

 e.setTitle("Corporate Deity"); // Этап 4: модификация копии

 se.insert(i, е); // Этап 5: вставка нового значения.

// Рекомендуемая позиция совпадает

// с позицией исходного элемента

}

Итак, при изменении «на месте» элементов контейнеров

set
и
multiset
следует помнить, что за сохранение порядка сортировки отвечает программист.

Совет 23. Рассмотрите возможность замены ассоциативных контейнеров сортированными векторами

Многие программисты STL, столкнувшись с необходимостью структуры данных с быстрым поиском, немедленно выбирают стандартные ассоциативные контейнеры set ,

multiset , map и multimap
. В этом выборе нет ничего плохого, но он не исчерпывает всех возможных вариантов. Если скорость поиска действительно важна, подумайте об использовании нестандартных хэшированных контейнеров (см. совет 25). При правильном выборе хэш-функций хэшированные контейнеры могут обеспечить поиск с постоянным временем (а при неправильном выборе хэш-функций или недостаточном размере таблиц быстродействие заметно снижается, но на практике это встречается относительно редко). Во многих случаях предполагаемое постоянное время поиска превосходит гарантированное логарифмическое время, характерное для контейнеров
set
,
map
и их
multi
– аналогов.

Поделиться с друзьями: