|
Программирование >> Включение нужных заголовков
лизированные составляющие (другой пример проблемы отсечения в STL приведен в совете 3). Бесспорно, эффективность является важным фактором, и предотвратить отсечение тоже необходимо, однако не все функторы малы и мономорфны. Одно из преимуществ объектов функций перед обычными функциями заключается в отсутствии ограничений на объем информации состояния. Некоторые объекты функций от природы упитанны , и очень важно, чтобы они могли передаваться алгоритмам STL так же просто, как и их тощие собратья. Столь же нереалистичен и запрет на полиморфные функторы. Иерархическое наследование и динамическое связывание относятся к числу важнейщих особенностей С++, и при проектировании классов функторов они могут принести такую же пользу, как и в других областях. Что такое классы функторов без наследования? С++ без ++ . Итак, необходимы средства, которые бы позволяли легко передавать большие и/или полиморфные объекты функций с соблюдением установленного в STL правила о передаче функторов по значению. Такие средства действительно существуют. Достаточно взять данные и/или полиморфные составляющие, которые требуется сохранить в классе функтора, перенести их в другой класс и сохранить в классе функтора указатель на этот новый класс. Рассмотрим пример создания класса полиморфного функтора с большим количеством данных: tempiate<typename Т> BPFC = Big Polymorphic class BPFC: Functor Class public Базовый класс описан unary function<T,void> { в совете 40 private: Widget w: Класс содержит большой объем int х: данных, поэтому передача по значению была бы неэффективной public: virtual void operatorO (const T& val) const: Виртуальная функция. создает проблему }; отсечения Мы выделяем все данные и виртуальные функции в класс реализации и создаем компактный, мономорфный класс, содержащий указатель на класс реализации: tempiate<typenanie Т> Новый класс реализации class BPFCImpl{ для измененного BPFC. private: Widget w; Все данные, ранее находившиеся int х: в BPFC, теперь размещаются в этом классе, virtual -BPFCImplO; В полиморфных классах нужен виртуальный деструктор, virtual void operatorO (const Т& val) const; friend class BPFC<T>; Разрешить BPFC доступ к данным tempiate<typename T> class BPFC: Компактная, мономорфная версия public unary function<T,void> { private: BPFCInipl<T> *plnip1: Все данные BPFC public: void operatorO(const T& val) const; Функция не является { виртуальной; вызов передается plInipl->operator()(val): BPFCImpl Реализация BFPC:: operatorO дает пример того, как должны строиться реализации всех виртуальных функций BPFC: они должны вызывать свои виртуальные прототипы из BPFCImpl. Полученный в результате класс функтора (BPFC) компактен и мономорфен, но при этом он предоставляет доступ к большому объему данных состояния и работает полиморфно. Материал изложен довольно кратко, поскольку описанные базовые приемы хорошо известны в кругах С++. В книге Effective С++ этой теме посвяшен совет 34. В книге Приемы объектно-ориентированного проектирования [6] соответствуюшая методика называется паттерн Bridge . Саттер в своей книге Exceptional С++ [8] использует термин идиома Pimpl . С позиций STL прежде всего необходимо помнить о том, что классы функторов, используюшие данную методику, должны поддерживать соответствующий механизм копирования. Если бы вы были автором приведенного выше класса BPFC, то вам пришлось бы позаботиться о том, чтобы копирующий конструктор выполнял осмысленные действия с объектом BPFCImpl, на который он ссылается. Возможно, простейшее решение заключается в организации подсчета ссылок при помощи указателя sharedptr из библиотеки Boost или его аналога (см. совет 50). В сущности, копирующий конструктор BPFC - единственное, о чем вам придется побеспокоиться в контексте данного примера, поскольку при передаче и получении функторов от функций STL всегда происходит копирование (помните, что говорилось выше о передаче по значению?). Из этого вытекают два требования: компактность и мономорфизм. Совет 39. Реализуйте предикаты в виде чистых функций Для начала разберемся с основными терминами. Предикатом называется функция, возвращающая тип bool (или другое значение, которое может быть автоматически преобразовано к bool). Предикаты широко используются в STL. В частности, функции сравнения в стандартных ассоциативных контейнерах представляют собой предикаты. Предикатные функции часто передаются в виде параметров таким алгоритмам, как findif, и различным алгоритмам сортировки (обзор алгоритмов сортировки приведен в совете 31). Чистой функцией называется функция, возвращаемое значение которой зависит только от параметров. Если f - чистая функция, а х и у - объекты, то возвращаемое значение f (х,у) может измениться только в случае изменения X или у. В С++ все данные, используемые чистыми функциями, либо передаются в виде параметров, либо остаются постоянными на протяжении всего жизненного цикла функции (естественно, такие постоянные данные объявляются с ключевым словом const). Если бы данные, используемые чистой функцией, могли изменяться между вызовами, то вызов этой функции в разные моменты времени с одинаковыми параметрами мог бы давать разные результаты, что противоречит определению чистой функции. Из сказанного должно быть понятно, что нужно сделать, чтобы предикаты были чистыми функциями. Мне остается лишь убедить читателя в том, что эта рекомендация обоснована. Для этого придется ввести еще один термин. Предикатным классом называется класс функтора, у которого функция operatorC) является предикатом, то есть возвращает true или fal se. Как и следует ожидать, во всех слзаях, когда STL ожидает получить предикат, может передаваться либо настоящий предикат, либо объект предикатного класса. Обещаю, что новых терминов больше не будет. Теперь давайте разберемся, почему следует выполнять рекомендацию данного совета. В совете 38 объяснялось, что объекты функций передаются по значению, поэтому при проектировании необходимо позаботиться о возможном копировании. Для объектов функций, являющихся предикатами, существует и другой аргумент в пользу специальной поддержки копирования. Алгоритмы могут создавать копии функторов и хранить их определенное время перед применением, причем некоторые реализации алгоритмов этим активно пользуются. Важнейшим следствием этого факта является то, что предикатные функции должны быть чистыми . Предположим, вы нарушили это ограничение. Ниже приведен плохо спроектированный класс предиката, который независимо от переданных аргументов возвращает true только один раз - при третьем вызове. Во всех остальных случаях возвращается fal se. class BadPredicate: Базовый класс описан public unary function<Widget.bool>{ в совете 40 public: BadPredicate():timesCalles(0){} Переменная timesCalled инициализируется нулем bool operatorO (const Widget&) { return -H-tirnesCalled == 3: private: size t timesCalled: Предположим, класс BadPredicate используется для исключения третьего объекта Wi dget из контейнера vector<Wi dget>: vector<Widget> vw: Создать вектор и заполнить его объектами Widget vww.erase(removeJf(vw.beginO, Удалить третий объект Widget. vw.endO, связь между erase и removejf BadPredicateO). описана в совете 32 vw.endO):
|
© 2006 - 2024 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки. |