Остается лишь разобраться со странными именами адаптеров. Перед нами самый настоящий пережиток прошлого STL. Когда впервые возникла необходимость в адаптерах, разработчики STL ориентировались на контейнеры указателей (с учетом недостатков таких контейнеров, описанных в советах 7, 20 и 33, это может показаться странным, но не стоит забывать, что контейнеры указателей поддерживают полиморфизм, а контейнеры объектов — нет). Когда понадобился адаптер для функций классов (MEMber FUNctions), его назвали mem_fun
. Только позднее разработчики поняли, что для контейнеров объектов понадобится другой адаптер, и для этой цели изобрели имя mem_fun_ref
. Конечно, выглядит не слишком элегантно, но… бывает, ничего не поделаешь. Пусть тот, кому никогда не приходилось жалеть о поспешном выборе имен своих компонентов, первым бросит камень.
Совет 42. Следите за тем, чтобы конструкция less означала 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()
}
Предположим, потребовалось создать контейнер multiset
, в котором объекты Widget
отсортированы по атрибуту maxSpeed
. Известно, что для контейнера multiset
используется функция сравнения less
, которая по умолчанию вызывает функцию operator<
класса Widget
. Может показаться, что единственный способ сортировки multiset
по атрибуту maxSpeed
основан на разрыве связи между less
и operator<
и специализации less
на сравнении атрибута maxSpeed
:
template<> // Специализация std::less
struct std::less: // для Widget: такой подход
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 // Специализация std::less
struct less >: // для boost::shared_ptr
public // (boost - пространство имен)
binary_function,
boost::shared_ptr, // Базовый класс описан
bool> { // в совете 40
bool operator()(const boost::shared_ptr& a,
const boost::shared_ptr& b) const {
return less()(a.get(), b.get()); // shared_ptr::get возвращает
} // встроенный указатель
}; // из объекта shared_ptr
}
В данном примере специализация выглядит вполне разумно, поскольку специализация less
всего лишь гарантирует, что порядок сортировки умных указателей будет совпадать с порядком сортировки их встроенных аналогов. К сожалению, наша специализация less для класса Widget
преподносит неприятный сюрприз.
Читать дальше