Книга: Эффективное использование STL
Совет 42. Следите за тем, чтобы конструкция less означала operator
Совет 42. Следите за тем, чтобы конструкция less<T> означала operator<
Допустим, объект класса Widget
обладает атрибутами weight
и maxSpeed
:
class Widget {
public:
…
size_t weight() const;
size_t maxSpeed() const;
…
}
Будем считать, что естественная сортировка объектов Widget
осуществляется по атрибуту weight
, что отражено в операторе <
класса Widget:
bool operator<(const Widget& lhs, const Widget& rhs) {
return lhs.weight()<rhs.weight();
}
Предположим, потребовалось создать контейнер multiset<Widget>
, в котором объекты Widget
отсортированы по атрибуту maxSpeed
. Известно, что для контейнера multiset<Widget>
используется функция сравнения less<Widget>
, которая по умолчанию вызывает функцию operator<
класса Widget
. Может показаться, что единственный способ сортировки multiset<Widget>
по атрибуту maxSpeed
основан на разрыве связи между less<Widget>
и operator<
и специализации less<Widget>
на сравнении атрибута maxSpeed
:
template<> // Специализация std::less
struct std::less<Widget>: // для Widget: такой подход
public // считается крайне нежелательным!
std::binаry_function<Widget,
Widget, // Базовый класс описан
bool> { // в совете 40
bool operator() (const Widget& lhs, const Widget& rhs) const {
return lhs.maxSpeed() < rhs.maxSpeed();
}
};
Поступать подобным образом не рекомендуется, но, возможно, совсем не по тем причинам, о которых вы подумали. Вас не удивляет, что этот фрагмент вообще компилируется? Многие программисты обращают внимание на то, что в приведенном фрагменте специализируется не обычный шаблон, а шаблон из пространства имен std
. «Разве пространство std
не должно быть местом священным, зарезервированным для разработчиков библиотек и недоступным для простых программистов? — спрашивают они. — Разве компилятор не должен отвергнуть любое вмешательство в творения бессмертных гуру C++?»
Вообще говоря, попытки модификации компонентов std
действительно запрещены, поскольку их последствия могут оказаться непредсказуемыми, но в некоторых ситуациях минимальные изменения все же разрешены. А именно, программистам разрешается специализировать шаблоны std
для пользовательских типов. Почти всегда существуют альтернативные решения, но в отдельных случаях такой подход вполне разумен. Например, разработчики классов умных указателей часто хотят, чтобы их классы при сортировке вели себя как встроенные указатели, поэтому специализация std::less
для типов умных указателей встречается не так уж редко. Далее приведен фрагмент класса shared_ptr
из библиотеки Boost
, упоминающегося в советах 7 и 50:
namespace std {
template<typename T> // Специализация std::less
struct less<boost::shared_ptr<T> >: // для boost::shared_ptr<T>
public // (boost - пространство имен)
binary_function<boost::shared_ptr<T>,
boost::shared_ptr<T>, // Базовый класс описан
bool> { // в совете 40
bool operator()(const boost::shared_ptr<T>& a,
const boost::shared_ptr<T>& b) const {
return less<T*>()(a.get(), b.get()); // shared_ptr::get возвращает
} // встроенный указатель
}; // из объекта shared_ptr
}
В данном примере специализация выглядит вполне разумно, поскольку специализация less
всего лишь гарантирует, что порядок сортировки умных указателей будет совпадать с порядком сортировки их встроенных аналогов. К сожалению, наша специализация less для класса Widget
преподносит неприятный сюрприз.
Программисты C++ часто опираются на предположения. Например, они предполагают, что копирующие конструкторы действительно копируют (как показано в совете 8, невыполнение этого правила приводит к удивительным последствиям). Они предполагают, что в результате взятия адреса объекта вы получаете указатель на этот объект (в совете 18 рассказано, что может произойти в противном случае). Они предполагают, что адаптеры bind1st
и not2
могут применяться к объектам функций (см. совет 40). Они предполагают, что оператор +
выполняет сложение (кроме объектов string
, но знак «+» традиционно используется для выполнения конкатенации строк), что оператор -
вычитает, а оператор ==
проверяет равенство. И еще они предполагают, что функция less
эквивалентна operator<
.
В действительности operator<
представляет собой нечто большее, чем реализацию less
по умолчанию — он соответствует ожидаемому поведению less
. Если less
вместо вызова operator<
делает что-либо другое, это нарушает ожидания программистов и вступает в противоречие с «принципом минимального удивления». Конечно, поступать так не стоит — особенно если без этого можно обойтись.
В STL нет ни одного случая использования less
, когда программисту бы не предоставлялась возможность задать другой критерий сравнения. Вернемся к исходному примеру с контейнером multiset<Widget>
, упорядоченному по атрибуту maxSpeed
. Задача решается просто: для выполнения нужного сравнения достаточно создать класс функтора практически с любым именем, кроме less
. Пример:
struct MaxSpeedCompare:
public binary_function<Widget, Widget, bool> {
bool operator()(const Widget& lhs, const Widget& rhs) const {
return lhs.maxSpeed() < rhs.maxSpeed();
}
};
При создании контейнера multiset
достаточно указать тип сравнения MaxSpeedCompare
, тем самым переопределяя тип сравнения по умолчанию (less<Widget>
):
multiset<Widget, MaxSpeedCompare> widgets;
Смысл этой команды абсолютно очевиден: мы создаем контейнер multiset
с элементами Widget
, упорядоченными в соответствии с классом функтора MaxSpeedCompare
. Сравните со следующим объявлением:
multiset<Widget> widgets;
В нем создается контейнер multiset
объектов Widget
, упорядоченных по стандартному критерию. Строго говоря, упорядочение производится по критерию less<Widget>
, но большинство программистов будет полагать, что сортировка производится функцией operator<
. Не нужно обманывать их ожидания и подменять определение less
. Если вы хотите использовать less
(явно или косвенно), проследите за тем, чтобы этот критерий был эквивалентен operator<
. Если объекты должны сортироваться по другому критерию, создайте специальный класс функтора и назовите его как-нибудь иначе.
- Совет 38. Проектируйте классы функторов для передачи по значению
- Совет 39. Реализуйте предикаты в виде «чистых» функций
- Совет 40. Классы функторов должны быть адаптируемыми
- Совет 41. Разберитесь, для чего нужны ptr_fun, mem_fun и mem_fun_ref
- Совет 42. Следите за тем, чтобы конструкция less означала operator
- Восстановление из резервной копии на системе-приемнике
- Неисправности акустических систем
- Особенности системы защиты данных в InterBase
- Система безопасности InterBase
- Что делать, если при установке принтера появляется сообщение Невозможно завершение операции. Подсистема печати недоступн...
- Системные переменные ROWS_AFFECTED, GDSCODE, SQLCODE, TRANSACTIONJD, CONNECTIONJD
- Системное программное обеспечение
- Хранение конфигурации в системном реестре
- Модификация системных таблиц
- 7 Система Цикл: долгосрочные цели
- Как сделать, чтобы компьютер выключался
- 3. Система конкурентных продаж (продажи по методу КЛИН)