Впрочем, не все так просто. Хотя элементы set/multiset
и не являются неизменными, реализации могут предотвратить их возможную модификацию. Например, оператор *
для set::iterator
может возвращать const T&
, то есть результат разыменования итератора 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
может быть вполне оправданно, поэтому я склонен показать, как это делается — а точнее, делается правильно и переносимо. Сделать это нетрудно, но при этом приходится учитывать тонкость, о которой забывают многие программисты — преобразование должно приводить к ссылке. В качестве примера рассмотрим вызов setTitle
, который, как было показано, не компилируется в некоторых реализациях:
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).setTitle("Corporate Deity"); // константность *i
}
Мы берем объект, на который ссылается i
, и сообщаем компилятору, что результат должен интерпретироваться как ссылка на (неконстантный) объект Employee
, после чего вызываем setTitle
для полученной ссылки. Я не буду тратить время на долгие объяснения и лучше покажу, почему альтернативное решение работает совсем не так, как можно было бы ожидать.
Многие программисты пытаются воспользоваться следующим кодом:
if (i != se.end()){ // Преобразовать *i
static_cast(*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
, чего мы и добивались.
Читать дальше