Совет 40. Классы функторов должны быть адаптируемыми
Совет 40. Классы функторов должны быть адаптируемыми
Предположим, у нас имеется список указателей Widget* и функция, которая по указателю определяет, является ли объект Widget «интересным»:
list<Widget*> WidgetPtrs:
bool isInteresting(const Widget *pw):
Если потребуется найти в списке первый указатель на «интересный» объект Widget, это делается легко:
list<Widget*>::iterator i =find_if(widgetPts.begin(),widgetPts.end(),
isInteesting);
if (i!=widgetPts.end()) {
// Обработка первого "интересного"
}// указателя на Widget
С другой стороны, если потребуется найти первый указатель на «неинтересный» объект Widget, следующее очевидное решение не компилируется:
list<Widget*>::iterator i = find_if(widgetPtrs.begin(),widgetPtrs.end(),
not1(isInteresting));// Ошибка! He компилируется
Перед not1 к функции isInteresting необходимо применить ptr_fun:
list<Widget*>::iterator i = find_if(widgetPtrs.begin(),widgetPtrs.end(),
not1(ptr_fun(isInteresting))); // Нормально
if (i!=widgetPtrs.end()){
// Обработка первого
}// "неинтересного" указателя
//на Widget
При виде этого решения невольно возникают вопросы. Почему мы должны применять ptr_fun к isInteresting перед not1? Что ptr_fun для нас делает и почему начинает работать приведенная выше конструкция?
Ответ оказывается весьма неожиданным. Вся работа ptr_fun сводится к предоставлению нескольких определений типов. Эти определения типов необходимы для not1, поэтому применение not1 к ptr_fun работает, а непосредственное применение not1 к isInteresting не работает. Примитивный указатель на функцию isInteresting не поддерживает определения типов, необходимые для not1.
Впрочем, not1 — не единственный компонент STL, предъявляющий подобные требования. Все четыре стандартных адаптера (not1, not2, bind1st и bind2nd), а также все нестандартные STL-совместимые адаптеры из внешних источников (например, входящие в SGI и Boost — см. совет 50), требуют существования некоторых определений типов. Объекты функций, предоставляющие необходимые определения типов, называются адаптируемыми; при отсутствии этих определений объект называется неадаптируемым. Адаптируемые объекты функций могут использоваться в контекстах, в которых невозможно использование неадаптируемых объектов, поэтому вы должны по возможности делать свои объекты функций адаптируемыми. Адаптируемость не требует никаких затрат, но значительно упрощает использование классов функторов клиентами.
Наверное, вместо туманного выражения «некоторые определения типов» вы бы предпочли иметь точный список? Речь идет об определениях argument_type, first_argument_type, second_argument_type и result_type, но ситуация осложняется тем, что разные классы функторов должны предоставлять разные подмножества этих имен. Честно говоря, если вы не занимаетесь разработкой собственных адаптеров, вам вообще ничего не нужно знать об этих определениях. Как правило, определения наследуются от базового класса, а говоря точнее — от базовой структуры. Для классов функторов, у которых operator() вызывается с одним аргументом, в качестве предка выбирается структура std::unary_function. Классы функторов, у которых operator() вызывается с двумя аргументами, наследуют от структуры std::binary_function.
Впрочем, не совсем так. unary_function и binary_function являются шаблонами, поэтому прямое наследование от них невозможно. Вместо этого при наследовании используются структуры, созданные на основе этих шаблонов, а для этого необходимо указать аргументы типов. Для unary_function задается тип параметра, получаемого функцией operator() вашего класса функтора, а также тип возвращаемого значения. Для binary_function количество типов увеличивается до трех: типы первого и второго параметров operator() и тип возвращаемого значения.
Пара примеров:
template<typename Т>
class MeetsThreshold: public std::unary_function<Widget,bool>{
private:
const T threshold; public:
Meets Threshold(const T& threshold);
bool operator() (const WidgetS) const;
};
struct WidgetNameCompare:
std::binary_function<Widget,Widget,bool>{
bool operator()(const Widget& lhs,const Widget& rhs) const;
};
В обоих случаях типы, передаваемые unary_function или binary_function, совпадают с типами, получаемыми и возвращаемыми функцией operator() класса функтора, хотя на первый взгляд несколько странно, что тип возвращаемого значения operator() передается в последнем аргументе unary_function или binary_function.
Возможно, вы заметили, что MeetsTheshold является классом, а WidgetNameCompare является структурой. MeetsTheshold обладает внутренним состоянием (переменная threshold), и для инкапсуляции этих данных логично воспользоваться именно классом. WidgetNameCompare состояния не имеет, поэтому и закрытые данные не нужны. Авторы классов функторов, в которых вся информация является открытой, часто объявляют структуры вместо классов — вероятно, только для того, чтобы им не приходилось вводить «public» перед базовым классом и функцией operator(). Выбор между классом и структурой при объявлении таких функторов определяется исключительно стилем программирования. Если вы еще не выработали собственного стиля и стараетесь имитировать профессионалов, учтите, что классы функторов без состояния в самой библиотеке STL (например, less<T>, plus<T> и т. д.) обычно записываются в виде структур.
Вернемся к определению WidgetNameCompare:
struct WidgetNameCompare:
std::binary_function<Widget,Widget.bool >{
bool operator()(const Widget& lhs,const Widget& rhs) const;
};
Хотя аргументы operator() относятся к типу const Widget&, шаблону binary_ function передается тип Widget. Обычно при передаче unary_function или binary_function типов, не являющихся указателями, ключевые слова const и знаки ссылки удаляются... только не спрашивайте, почему, — ответ на этот вопрос не интересен и не принципиален. Если вы сгораете от любопытства, напишите программу, в которой они не удаляются, и проанализируйте полученную диагностику компилятора. А если вы и после этого не утратите интерес к этой теме, посетите сайт boost.org (см. совет 50) и поищите на нем информацию об адаптерах объектов функций.
Если operator() получает параметры-указатели, ситуация меняется. Ниже приведена структура, аналогичная WidgetNameCompare, но работающая с указателями Widget*:
struct PtrWidgetNameCompare:
std::binary_function<const Widget*, const Widget*.bool>{
bool operator()(const Widget* Ihs. const Widget* rhs) const;
};
В этом случае типы, передаваемые binary_function, совпадают с типами, передаваемыми operator(). Общее правило для классов функторов, получающих или возвращающих указатели, заключается в том, что unary_function или binary_ function передаются в точности те типы, которые получает или возвращает operator().
Помните, что базовые классы unary_function и binary_function выполняют только одну важную функцию — они предоставляют определения типов, необходимые для работы адаптеров, поэтому наследование от этих классов порождает адаптируемые объекты функций. Это позволяет использовать в программах следующие конструкции:
list<Widget> widgets:
list<Widget>::reverse_iterator il=//Найти последний объект
find_if(widgets.rbegin(),widgets.rend(), //Widget, не соответствующий
not1(MeetsThreshold<int>(10))); //пороговому критерию 10
//(что бы это ни означало)
Widget w(аргументы конструктора): // Найти первый объект Widget.
list<Widget>::iterator i2 =// предшествующий w в порядке
find_if(widgets.begin(),widgets.end(),// сортировки, определенном
bind2nd(WidgetNameCompare().w));// WidgetNameCompare
Если бы классы функторов не определялись производными от unary_function или binary_function, ни один из этих примеров не компилировался бы, поскольку not1 и bind2nd работают только с адаптируемыми объектами функций.
Объекты функций STL построены по образцу функций С++, а функции С++ характеризуются единственным набором типов параметров и одним типом возвращаемого значения. В результате STL неявно подразумевает, что каждый класс функтора содержит единственную функцию operator(), типы параметров и возвращаемого значения которой должны передаваться unary_function или binary_ function (с учетом правил передачи ссылок и указателей, о которых говорилось ранее). Из этого следует одно важное обстоятельство: не поддавайтесь соблазну и не пытайтесь объединять функциональность WidgetnNameCompare и PtrWidgetCompare в одной структуре с двумя функциями operator(). В этом случае функтор будет адаптируемым по отношению лишь к одной из двух форм вызова (той, что использовалась при передаче параметров binary_function), а пользы от такого решения будет немного — наполовину адаптируемый функтор ничуть не лучше неадаптируемого.
Иногда в классе функтора бывает разумно определить несколько форм вызова, тем самым отказавшись от адаптируемости (примеры таких ситуаций приведены в советах 7, 20, 23 и 25), но это скорее исключение, а не правило. Адаптируемость важна, и о ней следует помнить при разработке классов функторов.
Более 800 000 книг и аудиокниг! 📚
Получи 2 месяца Литрес Подписки в подарок и наслаждайся неограниченным чтением
ПОЛУЧИТЬ ПОДАРОКЧитайте также
Значения атрибутов должны быть в кавычках
Значения атрибутов должны быть в кавычках В языке HTML значения атрибутов должны быть заключены в кавычки, только если в значение входят пробелы или специальные символы. Но в языке XHTML это недопустимо: все значения должны заключаться в кавычки. Например, в теге <td>,
Вы должны быть уверены в том, что ваш код работает
Вы должны быть уверены в том, что ваш код работает Как узнать, работает ли ваш код? Легко. Протестируйте его. Потом протестируйте еще раз. Протестируйте слева направо, потом справа налево. А теперь еще и сверху вниз!Возможно, вас беспокоит, что столь тщательное тестирование
6. Варианты оплаты, которые должны быть на вашем сайте
6. Варианты оплаты, которые должны быть на вашем сайте Если у вас на сайте предусмотрен наложенный платеж, простая оплата с помощью банка для физических и юридических лиц, webmoney, системы Робокасса и РБК-money, то у вас есть все возможные способы оплаты. Ваш клиент не
Основные разделы, которые должны быть на главной странице
Основные разделы, которые должны быть на главной странице Самые горячие вопросы, интересующие клиентов интернет-магазинов, – условия доставки, гарантия и возможные способы оплаты. Поэтому эти три раздела в обязательном порядке должны быть на самом видном месте сайта.
13. Ресурсы должны быть во владении объектов
13. Ресурсы должны быть во владении объектов РезюмеНе работайте вручную, если у вас есть мощные инструменты. Идиома С++ "выделение ресурса есть инициализация" (resource acquisition is initialization — RAII) представляет собой мощный инструмент для корректной работы с ресурсами. RAII позволяет
Совет 38. Проектируйте классы функторов для передачи по значению
Совет 38. Проектируйте классы функторов для передачи по значению Ни С, ни С++ не позволяют передавать функции в качестве параметров других функций. Вместо этого разрешается передавать указатели на функции. Например, объявление стандартной библиотечной функции qsort
Что вы должны знать
Что вы должны знать Данная книга представляет собой не учебник по Flash, а практическое руководство по изучению Flash 8 ActionScript. Подразумевается, что вы уже немного знакомы с рабочей средой Flash и имеете какой-то опыт работы с программой.При этом вы, также как и я, не обязаны быть
Если можем — не значит должны! Как выкрали рассказы Джерома Сэлинджера и обязана ли информация быть свободной? Евгений Золотов
Если можем — не значит должны! Как выкрали рассказы Джерома Сэлинджера и обязана ли информация быть свободной? Евгений Золотов Опубликовано 03 декабря 2013 Наука без морали погибнет. Эти слова принадлежат советскому академику Дмитрию Лихачёву,
До какой степени мы должны быть догматичными?
До какой степени мы должны быть догматичными? Список, представленный ниже, включает все свойства, кажущиеся существенными для создания высококачественного ПО ОО-методом. Наш список может показаться бескомпромиссным и даже догматичным. Какие заключения следует делать,
Должны ли исключения быть объектами?
Должны ли исключения быть объектами? Фанатики объектной ориентации (многие ли из тех, кто открыл красоту этого подхода, не рискуют стать его фанатиками?) могут критиковать представленный механизм за то, что исключения не являются гражданами первого сорта в программном
Отложенные классы как частичные интерпретации: классы поведения
Отложенные классы как частичные интерпретации: классы поведения Не все отложенные классы так близки к АТД как STACK. В промежутке между полностью абстрактным классом, таким как STACK, в котором все существенные компоненты отложены, и эффективным классом, таким как FIXED_STACK,
Изменения. Быть или не быть
Изменения. Быть или не быть Предположим, команда пришла к выводу, что «мы слишком слабо общались внутри команды, поэтому мы постоянно мешали друг другу и переделывали архитектурные решения».Что нам с этим делать? Организовать ежедневные встречи для обсуждения
Резюме: Аутсорсинговые системы должны быть защищены
Резюме: Аутсорсинговые системы должны быть защищены Из этого исследования можно сделать два вывода. Во-первых, аутсорсинг функций компании не означает аутсорсинга обязанностей по поддержанию безопасности. На самом деле эти обязанности должны быть уточнены и
Быть или не быть Microsoft Office для Linux Максим Плакса
Быть или не быть Microsoft Office для Linux Максим Плакса Опубликовано 13 февраля 2013 Слух о предстоящем выходе версии Microsoft Office для Linux появился 5 февраля. Об этом якобы проговорился автору сайта Phoronix один из сотрудников Microsoft во время проходившего в Брюсселе
Здравствуй, племя незнакомое, или Почему инопланетяне должны быть похожими на нас? Владимир Комен, генеральный директор WIT Company
Здравствуй, племя незнакомое, или Почему инопланетяне должны быть похожими на нас? Владимир Комен, генеральный директор WIT Company Опубликовано 25 февраля 2013 Наряду с круглой датой — 50-летием первого полёта человека в космос — проходили мероприятия