В итоге выяснилось, что создать контейнер с промежуточными объектами, удовлетворяющий всем требованиям к контейнеру STL, vector был сохранен, но с практической точки зрения это несущественно. Главное — помните, что vector не удовлетворяет требованиям к контейнерам STL, что им лучше не пользоваться и что существуют альтернативные структуры данных deque и bitset, почти всегда способные заменить vector.
Ассоциативные контейнеры
Ассоциативные контейнеры по некоторым характеристикам схожи с последовательными контейнерами, однако между этими категориями существует ряд принципиальных различий. Так, содержимое ассоциативных контейнеров автоматически сортируется; анализ содержимого производится по критерию эквивалентности, а не равенства; контейнеры set и map не могут содержать дубликатов, а контейнеры map и multimap обычно игнорируют половину данных в каждом из содержащихся в них объектов. Да, ассоциативные контейнеры являются контейнерами, но они определенно выделяются в самостоятельную категорию.
В этой главе мы рассмотрим основное понятие эквивалентности; проанализируем важное ограничение, установленное для функций сравнения; познакомимся с пользовательскими функциями сравнения для ассоциативных контейнеров указателей; обсудим официальные и практические аспекты неизменности ключа, а также пути повышения эффективности ассоциативных контейнеров.
В STL отсутствуют контейнеры на базе хэш-таблиц, поэтому глава завершается примерами двух распространенных (хотя и нестандартных) реализаций. Несмотря на отсутствие хэш-таблиц в STL, вам не придется реализовывать их самостоятельно или обходиться другими контейнерами. Существует немало готовых качественных реализаций.
Задача сравнения объектов возникает в STL очень часто. Например, если функция find ищет в интервале первый объект с заданным значением, она должна уметь сравнивать два объекта и узнавать, совпадают ли их значения. При попытке включения нового элемента в множество функция set::insert должна проверить, не существует ли данное значение в контейнере.
Совет 19. Помните о различиях между равенством и эквивалентностью
Алгоритм find и функция set::insert являются типичными представителями семейства функций, проверяющих совпадение двух величин, однако делают это они по-разному. Для find совпадением считается ==. Функция set::insert проверяет отношение <. Таким образом, по одному определению два объекта могут иметь одинаковые значения, тогда как по другому определению они будут различаться. Отсюда следует, что для эффективного использования STL необходимо понимать различия между равенством и эквивалентностью.
Формальное определение равенства основано на использовании оператора ==. Если результат выражения x==y равен true, значит, x и y имеют одинаковые значения, а если false — разные. В целом определение весьма прямолинейное, хотя следует помнить о том, что из равенства значений не следует равенство всех полей данных. Предположим, класс Widget хранит внутренние данные о времени последнего обращения:
class Widget {
public:
…
private:
TimeStamp lastAccessed;
};
Для класса Widgetможно определить оператор ==, игнорирующий значение этого поля:
bool operator==(const Widgets lhs, const Widgets rhs) {
// Поле lastAccessed игнорируется
}
В этом случае два объекта Widget будут считаться равными даже в том случае, если их поля lastAccessed содержат разные значения.
Эквивалентность основана на относительном порядке значений объектов в отсортированном интервале. Проще всего рассматривать ее в контексте порядка сортировки, являющегося частью любого стандартного ассоциативного контейнера (то есть set, multiset, map и multimap). Два объекта x и y считаются эквивалентными по отношению к порядку сортировки, используемому ассоциативным контейнером c, если ни один из них не предшествует другому в порядке сортировки c. На первый взгляд такая формулировка кажется запутанной, но на практике все просто. Возьмем контейнер set. Два объекта Widget, w1 и w2, имеют эквивалентные значения по отношению к s, если ни один из них не предшествует другому в порядке сортировки s. Стандартная функция сравнения для set — less — по умолчанию просто вызывает operator< для объектов Widget, поэтому w1 и w2 будут иметь значения, эквивалентные по отношению к operator< если истинно следующее выражение:
!(w1
&& // и
!(w2