Сказанное прежде всего касается контейнеров map
и multimap
, поскольку программы, пытающиеся изменить значение ключа в этих контейнерах, не будут компилироваться:
map m;
…
m.begin()->first = 10; // Ошибка! Изменение ключей
// в контейнере map запрещено
multimap mm;
mm.begin()->first = 20; // Ошибка! Изменение ключей
// в контейнере multimap запрещено
Дело в том, что элементы объекта типа map
или multimap
относятся к типу pair
. Ключ относится к типу const K
и поэтому не может изменяться. Впрочем, его все же можно изменить с применением const_cast
, как показано ниже. Хотите — верьте, хотите — нет, но иногда это даже нужно.
Обратите внимание: в заголовке этого совета ничего не сказано о контейнерах map
и multimap
. Для этого есть веские причины. Как показывает предыдущий пример, модификация ключа «на месте» невозможна для map
и multimap
(без применения преобразования const_cast
), но может быть допустима для set
и multiset
. Для объектов типа set
и multiset
в контейнере хранятся элементы типа T
, а не const T
. Следовательно, элементы контейнеров set
и mu l
tiset можно изменять в любое время, и преобразование const_cast
для этого не требуется (вообще говоря, дело обстоит не так просто, но не будем забегать вперед).
Сначала выясним, почему элементы set
и multiset
не имеют атрибута const
. Допустим, у нас имеется класс Employee
:
class Employee {
public:
…
const string& name() const; // Возвращает имя работника
void setName(const string& name); // Задает имя работника
const string& title() const; // Возвращает должность
void setTitle(const string& title); // Задает должность
int idNumber() const; // Возвращает код работника
…
}
Объект содержит разнообразные сведения о работнике. Каждому работнику назначается уникальный код, возвращаемый функцией idNumber
. При создании контейнера set
с объектами Employee
было бы вполне разумно упорядочить его по кодам работников:
struct IDNumberLess:
public binary_function { // См. совет 40
bool operator() (const Employees lhs, const Employees rhs) const {
return lhs.idNumber() < rhs. IdNumber();
}
}
typedef set EmplIDSet;
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
не следует изменять ключевую часть (то есть ту часть элемента, которая влияет на порядок сортировки в контейнере). В противном случае целостность данных контейнера будет нарушена, операции с контейнером начнут приводить к непредсказуемым результатам, и все это произойдет по вашей вине. С другой стороны, это ограничение относится только к ключевым атрибутам объектов, содержащихся в контейнере. Остальные атрибуты объектов находятся в вашем полном распоряжении — изменяйте на здоровье!
Читать дальше