EmplIDSet se; // Контейнер set объектов
// Employee, упорядоченных
// по коду
С практической точки зрения код работника является
Employee selectedID; // Объект работника с заданным кодом
…
EmpIDSet::iterator = se.find(selectedID);
if (i!=se.end) {
i->setTitle("Corporate Deity"); // Изменить должность
}
Поскольку мы всего лишь изменяем вторичный атрибут данных, не влияющий на порядок сортировки набора, этот фрагмент не приведет к порче данных, и он вполне допустим.
Спрашивается, почему нельзя применить ту же логику к ключам контейнеров map
и multimap
? Почему бы не создать контейнер map
, ассоциирующий работников со страной, в которой они живут; контейнер с функцией сравнения IDNumberLess
, как в предыдущем примере? И почему бы в таком контейнере не изменить должность без изменения кода работника, как в предыдущем примере?
Откровенно говоря, мне это кажется вполне логичным, однако мое личное мнение в данном случае несущественно. Важно то, что Комитет по стандартизации решил, что ключи map/multimap
должны быть неизменными (const
), а значения set/multiset
— не должны.
Значения в контейнерах set/multiset
не являются неизменными, поэтому попытки их изменения обычно нормально компилируются. Данный совет лишь напоминает вам о том, что при модификации элементов set/multiset
не следует изменять ключевую часть (то есть ту часть элемента, которая влияет на порядок сортировки в контейнере). В противном случае целостность данных контейнера будет нарушена, операции с контейнером начнут приводить к непредсказуемым результатам, и все это произойдет по вашей вине. С другой стороны, это ограничение относится только к ключевым атрибутам объектов, содержащихся в контейнере. Остальные атрибуты объектов находятся в вашем полном распоряжении — изменяйте на здоровье!
Впрочем, не все так просто. Хотя элементы set/multiset
и не являются неизменными, реализации могут предотвратить их возможную модификацию. Например, оператор *
для set
может возвращать 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
, который, как было показано, не компилируется в некоторых реализациях: