Остается лишь разобраться со странными именами адаптеров. Перед нами самый настоящий пережиток прошлого 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& Ihs. const Widget& rhs) {
return lhs.weight()
}
Предположим, потребовалось создать контейнер multiset, в котором объекты Widget отсортированы по атрибуту maxSpeed. Известно, что для контейнера multiset используется функция сравнения less, которая по умолчанию вызывает функцию operator<класса Widget. Может показаться, что единственный способ сортировки multi set по атрибуту maxSpeed основан на разрыве связи между less и operator<и специализации less на сравнении атрибута maxSpeed:
template<> // Специализация std::less
struct std::less; // для Widget: такой подход
public // считается крайне нежелательным!
std::binаry_function
Widget,// Базовый класс описан
bool>{// в совете 40
bool operator() (const Widget&Ihs. const Widget&rhs) const
{
return lhs.maxSpeed()
}
};
Поступать подобным образом не рекомендуется, но, возможно, совсем не по тем причинам, о которых вы подумали. Вас не удивляет, что этот фрагмент вообще компилируется? Многие программисты обращают внимание на то, что в приведенном фрагменте специализируется не обычный шаблон, а шаблон из пространства имен std. «Разве пространство std не должно быть местом священным, зарезервированным для разработчиков библиотек и недоступным для простых программистов? — спрашивают они. — Разве компилятор не должен отвергнуть любое вмешательство в творения бессмертных гуру С++?»
Вообще говоря, попытки модификации компонентов 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 преподносит неприятный сюрприз.
Программисты С++ часто опираются на предположения. Например, они предполагают, что копирующие конструкторы действительно копируют,(как показано в совете 8, невыполнение этого правила приводит к удивительным последствиям). Они предполагают, что в результате взятия адреса объекта вы получаете указатель на этот объект (в совете 18 рассказано, что может произойти в противном случае). Они предполагают, что адаптеры bind1st и not2 могут применяться к объектам функций (см. совет 40). Они предполагают, что оператор + выполняет сложение (кроме объектов string, но знак «+» традиционно используется для выполнения конкатенации строк), что оператор - вычитает, а оператор == проверяет равенство. И еще они предполагают, что функция less эквивалентна operator<
В действительности operator< представляет собой нечто большее, чем реализацию less по умолчанию — он соответствует ожидаемому поведению less. Если less вместо вызова operator< делает что-либо другое, это нарушает ожидания программистов и вступает в противоречие с «принципом минимального удивления». Конечно, поступать так не стоит — особенно если без этого можно обойтись.
В STL нет ни одного случая использования less, когда программисту бы не предоставлялась возможность задать другой критерий сравнения. Вернемся к исходному примеру с контейнером multiset, упорядоченному по атрибуту maxSpeed. Задача решается просто: для выполнения нужного сравнения достаточно создать класс функтора практически с любым именем, кроме less. Пример:
struct MaxSpeedCompare:
public binary_function {
bool operator()(const Widget& Ihs.const Widget& rhs) const
{
return lhs,maxSpeed()
}
};
При создании контейнера multiset достаточно указать тип сравнения MaxSpeedCompare, тем самым переопределяя тип сравнения по умолчанию (less):
multiset widgets;
Смысл этой команды абсолютно очевиден: мы создаем контейнер multiset с элементами Widget, упорядоченными в соответствии с классом функтора MaxSpeedCompare. Сравните со следующим объявлением:
multiset widgets;
В нем создается контейнер multiset объектов Widget, упорядоченных по стандартному критерию. Строго говоря, упорядочение производится по критерию less, но большинство программистов будет полагать, что сортировка производится функцией operator< Не нужно обманывать их ожидания и подменять определение less. Если вы хотите использовать less (явно или косвенно), проследите за тем, чтобы этот критерий был эквивалентен operator< Если объекты должны сортироваться по другому критерию, создайте специальный класс функтора и назовите его как-нибудь иначе.
Программирование в STL
STL традиционно характеризуется как совокупность контейнеров, итераторов, алгоритмов и объектов функций, однако программирование в STL заключает в себе нечто большее. Этот термин означает, что программист способен правильно выбирать между циклами, алгоритмами или функциями контейнеров; знает, в каких случаях equal_range предпочтительнее lower_bound, когда lower_bound предпочтительнее find и когда find превосходит equal_range. Термин означает, что программист умеет повышать быстродействие алгоритма посредством замены функций эквивалентными функторами и избегает написания непереносимого или плохо читаемого кода. Более того, к этому понятию даже относится умение читать сообщения об ошибках компилятора, состоящие из нескольких тысяч символов, и хорошее знание Интернет-ресурсов, посвященных STL (документация, расширения и даже полные реализации).
Да, для программирования в STL необходимо много знать, и большая часть этой информации приведена в данной главе.
Совет 43. Используйте алгоритмы вместо циклов
Каждому алгоритму передается по крайней мере одна пара итераторов, определяющих интервал объектов для выполнения некоторой операции. Так, алгоритм min_element находит минимальное значение в интервале, алгоритм accumulate вычисляет сводную величину, характеризующую интервал в целом (см. совет 37), а алгоритм partition делит элементы интервала на удовлетворяющие и не удовлетворяющие заданному критерию (см. совет 31). Чтобы алгоритм мог выполнить свою задачу, он должен проанализировать каждый объект в переданном интервале (или интервалах), для чего объекты в цикле перебираются от начала интервала к концу. Некоторые алгоритмы (такие как find и find_if) могут вернуть управление до завершения полного перебора, но и в этих алгоритмах задействован внутренний цикл. Ведь даже алгоритмы find и find_if должны проанализировать все элементы интервала, прежде чем принять решение об