Предположим, потребовалось создать контейнер multiset
, в котором объекты Widget
отсортированы по атрибуту maxSpeed
. Известно, что для контейнера multiset
используется функция сравнения less
, которая по умолчанию вызывает функцию operator<
класса Widget
. Может показаться, что единственный способ сортировки multiset
по атрибуту maxSpeed
основан на разрыве связи между less
и operator<
и специализации less
на сравнении атрибута maxSpeed
:
template<> // Специализация std::less
struct std::less
public // считается крайне нежелательным!
std::binаry_function
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
struct less
public // (boost - пространство имен)
binary_function
boost::shared_ptr
bool> { // в совете 40
bool operator(const boost::shared_ptr
const boost::shared_ptr
return less
} // встроенный указатель
}; // из объекта shared_ptr
}
В данном примере специализация выглядит вполне разумно, поскольку специализация less
всего лишь гарантирует, что порядок сортировки умных указателей будет совпадать с порядком сортировки их встроенных аналогов. К сожалению, наша специализация less для класса Widget
преподносит неприятный сюрприз.
Программисты C++ часто опираются на предположения. Например, они предполагают, что копирующие конструкторы действительно копируют (как показано в совете 8, невыполнение этого правила приводит к удивительным последствиям). Они предполагают, что в результате взятия адреса объекта вы получаете указатель на этот объект (в совете 18 рассказано, что может произойти в противном случае). Они предполагают, что адаптеры bind1st
и not2
могут применяться к объектам функций (см. совет 40). Они предполагают, что оператор +
выполняет сложение (кроме объектов string
, но знак «+» традиционно используется для выполнения конкатенации строк), что оператор -
вычитает, а оператор ==
проверяет равенство. И еще они предполагают, что функция less
эквивалентна operator<
.
В действительности operator<
представляет собой нечто большее, чем реализацию less
по умолчанию — он соответствует less
. Если less
вместо вызова operator<
делает что-либо другое, это нарушает ожидания программистов и вступает в противоречие с «принципом минимального удивления». Конечно, поступать так не стоит — особенно если без этого можно обойтись.