Впрочем, не все так просто. Хотя элементы set/multiset и не являются неизменными, реализации могут предотвратить их возможную модификацию. Например, оператор* для set<T>: iterator может возвращать const Т&, то есть результат разыменования итератора set может быть ссылкой на const-элемент контейнера! При такой реализации изменение элементов set и multiset невозможно, поскольку при любом обращении к элементу автоматически добавляется объявление const.
Законны ли такие реализации? Может, да, а может — нет. По этому вопросу Стандарт высказывается недостаточно четко, и в соответствии с законом Мерфи разные авторы интерпретируют его по-разному. В результате достаточно часто встречаются реализации STL, в которых следующий фрагмент компилироваться не будет (хотя ранее говорилось о том, что он успешно компилируется):
EmplIDSet se;// Контейнер set объектов
// Employee, упорядоченных
// по коду
Employee selectedID; // Объект работника с заданным кодом
EmpIDSet::iterator=se.find(selectedID);
if (i!=se.end()){
i->setTitle('Corporate Deity'); // Некоторые реализации STL
}; // выдают ошибку в этой строке
Вследствие неоднозначности стандарта и обусловленных ею различий в реализациях программы, пытающиеся модифицировать элементы контейнеров set и multiset, не переносимы.
Что из этого следует? К счастью, ничего особенно сложного:
•если переносимость вас не интересует, если вы хотите изменить значение элемента в контейнере set/multiset и ваша реализация STL это разрешает — действуйте. Помните о том, что ключевая часть элемента (то есть часть элемента, определяющая порядок сортировки элементов в контейнере) должна сохраниться без изменений;
•если программа должна быть переносимой, элементы контейнеров set/ multiset модифицироваться не могут (по крайней мере, без преобразования const_cast).
Кстати, о преобразованиях. Вы убедились в том, что изменение вторичных данных элемента set/multiset может быть вполне оправданно, поэтому я склонен показать, как это делается — а точнее, делается правильно и переносимо. Сделать это нетрудно, но при этом приходится учитывать тонкость, о которой забывают многие программисты — преобразование должно приводить к
EmpIDSet::iterator i=se.find(selectedID);
if (i!=se.end()) {
i->setTitle('Corporate Deity'); // Некоторые реализации STL
}// выдают ошибку в этой строке,
// поскольку *i имеет атрибут const
Чтобы этот фрагмент нормально компилировался и работал, необходимо устранить константность *i. Правильный способ выглядит так:
if (i!=se.end()){// Устранить
const_cast<Empioyee&>(*i).setTitle('Corporate Deity'); // константность *i
}
Мы берем объект, на который ссылается
Многие программисты пытаются воспользоваться следующим кодом:
if (i!=se.end()){// Преобразовать *i
static_cast<Employee>(*i).setTitle('Corporate Deity'); // к Employee
}
Приведенный фрагмент эквивалентен следующему:
if (i!=se.end()){// То же самое.
((Employee)(*i)).setTitle('Corporate Deity');
// но с использованием
} // синтаксиса С
Оба фрагмента компилируются, но вследствие эквивалентности работают неправильно. На стадии выполнения объект
if (i!=se.end()){
Employee tempCopy(*i);// Скопировать *i в tempCopy
tempCopy.setTitle('Corporate Deity');// Изменить tempCopy
}
Становится понятно, почему преобразование должно приводить именно к ссылке — тем самым мы избегаем создания нового объекта. Вместо этого результат преобразования представляет собой ссылку на
Все сказанное хорошо подходит для контейнеров set и multiset, но при переходе к map/multimap ситуация усложняется. Вспомните, что map<K,V> и multimap<K,V> содержат элементы типа pair<const K,V>. Объявление const означает, что первый компонент пары
Несомненно, вы слышали, что подобные преобразования рискованны. Надеюсь, вы будете избегать их по мере возможности. Выполняя преобразование, вы временно отказываетесь от страховки, обеспечиваемой системой типов, а описанные проблемы дают представление о том, что может произойти при ее отсутствии.
Многие преобразования (включая только что рассмотренные) не являются абсолютно необходимыми. Самый безопасный и универсальный способ модификации элементов контейнера set, multiset, map или multimap состоит из пяти простых шагов.
1.Найдите элемент, который требуется изменить. Если вы не уверены в том, как сделать это оптимальным образом, обратитесь к рекомендациям по поводу поиска в совете 45.
2.Создайте копию изменяемого элемента. Помните, что для контейнеров map/ multimap первый компонент копии не должен объявляться константным — ведь именно его мы и собираемся изменить!
3.Удалите элемент из контейнера. Обычно для этого используется функция erase (см. совет 9).
4.Измените копию и присвойте значение, которое должно находиться в контейнере.
5.Вставьте новое значение в контейнер. Если новый элемент в порадке сортировки контейнера находится в позиции удаленного элемента или в соседней позиции, воспользуйтесь «рекомендательной» формой insert, повышающей эффективность вставки от логарифмической до постоянной сложности. В качестве рекомендации обычно используется итератор, полученный на шаге 1.
EmpIDSet: iterator i= se.find(selectedlD);
if (i!=se.end()) { Employee e(*i);
se.erase(i++):
// Этап 1: поиск изменяемого элемента
//Этап 2: копирование элемента
//Этап 3: удаление элемента.
//Увеличение итератора
//сохраняет его
//действительным (см. совет 9)
e.setTitle('Corporate Deity'); // Этап 4: модификация копии
se.insert(i,е):
// Этап 5: вставка нового значения.
//Рекомендуемая позиция совпадает
//с позицией исходного элемента
Итак, при изменении «на месте» элементов контейнеров set и multiset следует помнить, что за сохранение порядка сортировки отвечает программист.
Совет 23. Рассмотрите возможность замены ассоциативных контейнеров сортированными векторами
Многие программисты STL, столкнувшись с необходимостью структуры данных с быстрым поиском, немедленно выбирают стандартные ассоциативные контейнеры set, multiset, map и multimap. В этом выборе нет ничего плохого, но он не исчерпывает всех возможных вариантов. Если скорость поиска действительно важна, подумайте об использовании нестандартных хэшированных контейнеров (см. совет 25). При правильном выборе хэш-функций хэшированные контейнеры могут обеспечить поиск с постоянным временем (а при неправильном выборе хэш-функций или недостаточном размере таблиц быстродействие заметно снижается, но на практике это встречается относительно редко). Во многих случаях предполагаемое постоянное время поиска превосходит гарантированное логарифмическое время, характерное для контейнеров set, map и их multi -аналогов.
Даже если гарантированное логарифмическое время поиска вас устраивает, стандартные ассоциативные контейнеры не всегда являются лучшим выбором. Как ни странно, стандартные