EmpIDSet::iterator i = se.find(selectedID);
if (i != se.end) {
i->setTitle("Corporate Deity"); // Некоторые реализации STL
} // выдают ошибку в этой строке,
// поскольку *i имеет атрибут const
Чтобы этот фрагмент нормально компилировался и работал, необходимо устранить константность *i
. Правильный способ выглядит так:
if (i != se.end){ // Устранить
const_cast
}
Мы берем объект, на который ссылается i
, и сообщаем компилятору, что результат должен интерпретироваться как ссылка на (неконстантный) объект Employee
, после чего вызываем setTitle
для полученной ссылки. Я не буду тратить время на долгие объяснения и лучше покажу, почему альтернативное решение работает совсем не так, как можно было бы ожидать.
Многие программисты пытаются воспользоваться следующим кодом:
if (i != se.end){ // Преобразовать *i
static_cast
}
Приведенный фрагмент эквивалентен следующему:
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
и multimap
содержат элементы типа pair
. Объявление const
означает, что первый компонент пары map
и multimap
.
Несомненно, вы слышали, что подобные преобразования рискованны. Надеюсь, вы будете избегать их по мере возможности. Выполняя преобразование, вы временно отказываетесь от страховки, обеспечиваемой системой типов, а описанные проблемы дают представление о том, что может произойти при ее отсутствии.
Многие преобразования (включая только что рассмотренные) не являются абсолютно необходимыми. Самый безопасный и универсальный способ модификации элементов контейнера set
, multiset
, map
или multimap
состоит из пяти простых шагов.
1. Найдите элемент, который требуется изменить. Если вы не уверены в том, как сделать это оптимальным образом, обратитесь к рекомендациям по поводу поиска в совете 45.
2. Создайте копию изменяемого элемента. Помните, что для контейнеров map/multimap
первый компонент копии не должен объявляться константным — ведь именно его мы и собираемся изменить!
3. Удалите элемент из контейнера. Обычно для этого используется функция erase
(см. совет 9).
4. Измените копию и присвойте значение, которое должно находиться в контейнере.