Теперь s
содержит два экземпляра числа 10, и было бы логично предположить, что при вызове equal_range
мы получим пару итераторов, описывающих интервал с обеими копиями. Однако это невозможно. Функция equal_range
, несмотря на свое название, определяет интервал не равных, а s
говорит, что 10A и 10B не эквивалентны, поэтому они не могут оказаться в интервале, определяемом функцией equal_range
.
Ну что, убедились? Функция сравнения всегда должна возвращать false для равных величин, в противном случае нарушается работа всех стандартных ассоциативных контейнеров (независимо от того, могут они содержать дубликаты или нет).
Строго говоря, функции сравнения, используемые для сортировки ассоциативных контейнеров, должны определять для сравниваемых объектов порядок строгой квазиупорядоченности (strict weak ordering); аналогичное ограничение действует и для функций сравнения, передаваемых алгоритмам, — таким, как sort
(см. совет 31). Если вас заинтересуют подробности того, что понимается под строгой квазиупорядоченностью, информацию можно найти во многих серьезных руководствах по STL, в том числе «The C++ Standard Library» [3], «Generic Programming аnd the STL» [4] и на web-сайте SGI, посвященном STL [21]. Особых откровений не ждите, но одно из требований строгой квазиупорядоченности относится непосредственно к данному совету. Требование заключается в следующем: функция, определяющая строгую квазиупорядоченность, должна возвращать false
при получении двух копий одного значения.
Совет 22. Избегайте изменения ключа «на месте» в контейнерах set и multiset
Понять смысл этого совета нетрудно. Контейнеры set/multiset
, как и все стандартные ассоциативные контейнеры, хранят свои элементы в отсортированном порядке, и правильное поведение этих контейнеров зависит от сохранения этого порядка. Если изменить значение элемента в ассоциативном контейнере (например заменить 10 на 1000), новое значение окажется в неправильной позиции, что нарушит порядок сортировки элементов в контейнере.
Сказанное прежде всего касается контейнеров map
и multimap
, поскольку программы, пытающиеся изменить значение ключа в этих контейнерах, не будут компилироваться:
map
…
m.begin->first = 10; // Ошибка! Изменение ключей
// в контейнере map запрещено
multimap
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
и mul
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
bool operator (const Employees lhs, const Employees rhs) const {
return lhs.idNumber < rhs. IdNumber;
}
}
typedef set