Д. Вандевурд, Н.М. Джосаттис - Шаблоны C++. Справочник разработчика (2003) (1160769), страница 81
Текст из файла (страница 81)
22.4. Функторы-классы №1пс1пс[е <зес> с1азз Регзоп с1азз РегзопЯогГСг№сегйоп ( риЬ11с: Ьоо1 орегагог() (Регзоп сопзсй р1, Регзоп сопзгй р2) сопзс ( // Возвращает информацию, меньше ли р1, чем р2 1с[ № () ( все(:: ЗЕС<РЕГЗОП, ЗЫ:: 1ЕЗВ<РЕГЗОП» СО, С1; // Сортировка с помощью оператора < Зье[::ЗЕс<РЕГЗОП, ЗЫ::дГЕагЕГ<РЕГВОП» С2; // Сортировка с помощью оператора > вес[::зес<Регзоп, РегзопЯогсСгЫегйоп> сЗ; // Сортировка по критерию пользователя сО = с1; с1 = с2; // Корректная операция: типы идентичны // ОШИБКА: типы различны 1№ (с1 == сЗ) ( // ОШИБКА: типы различны пашезрасе зсг) ( сешр1асе <Сурепаще Т> с1азз 1езз ( РпЬ11с: Ьоо1 орегасог()(т сопзсй х, т сопвсй у) сопвс 5 Реальная рсализапия этого шаблона несколько отличается от приведенной, поскольку класс 1евв является производным от класса вес::ыпаку еипссзоп.
Более подробную информацию по этому вопросу можно найти и разделе 8.2.4 [18). Во всех трех объявлениях объектов класса зес тип элементов множества и критерий сортировки передаются в виде аргументов шаблонов. Стандартный шаблон зсс[:: 1евз определен таким образом, чтобы возвращать посредством "вызова функции" результат применения оператора <. Приведенная ниже упрощенная реализация этого шаблона поясняет суть дела . 5 450 Глава 22. Объекты-функции и обратные вызовы тесцгп х < уг ) ); ) Подобный вид имеет и шаблон вес): г дтеасет. Поскольку все три критерия сортировки различны, полученные в результате объекты класса век принадлежат разным типам. Поэтому любая попытка присвоения или сравнения двух таких обьектов приводит к ошибке времени компиляции (операнды оператора сравнения должны принадлежать одному и'тому же типу).
Приведенный пример может показаться довольно простым, однако до появления шаблонов критерий сортировки можно было задавать только с помощью содержащегося в контейнере поля указателя на функцию. При этом любое несовпадение типов могло остаться незамеченным до тех пор, пока программа не запускалась на выполнение (а для его выявления в некоторых случаях требовались немалые усилия и ловкость детектива).
22.5. Определение функторов В предыдущем примере, демон/р(рирующем применение стандартного класса вес, проиллюстрирован только один способ выбора вида функторов. В данном разделе рассматривается несколько других подходов. 22.5.1. Функторы в роли аргументов типа шаблонов Один из способов передачи функтора — сделать его аргументом типа в шаблоне. Однако тип сам по себе не является функтором, поэтому пользовательская функция или класс должны создавать объект-функтор данного типа.
Конечно же, это возмолщо только для функторовклассов, но не для указателей на функции. Посщздние сами по себе не определяют поведение объекта. Аналогичным образом приходим к заключению, что такой механизм не подходит для передачи фуикгора-класса, в котором инкапсулирована некоторая информация о состоянии (поскольку в самих этих типах никакое состояние не инкапсулировано; для передачи югформации о состоянии понадобился бы отдельный объект такого типа).
Ниже приведен общий вид шаблона функции, для которой критерий сортировки задается в виде функтора-класса. сещр1асе <сурепаще РО> уо3.6 щу ветс ( .. ) ( // Создание объекта-функции 1й (сщр(х,у)) ( // Сравнение двух величин с // помощью объекта-функции '' 22.5. Определение функторов 45) // Вызов функции с функтором щу веге<не<1::1евв<...» (...) В продемонстрированном подходе выбор кода, с помощью которого проводится сравнение, происходит на этапе компиляции. А поскольку операцию сравнения можно сделать встраиваемой, компиляторы с хорошей оптимизацией способны сгенерировать код, в котором вызовы функтора заменены необходимыми операциями. В идеале оптимизатор должен также обладать способностью избегать выделения памяти для объекта сщр, однако на практике редко встречаются компиляторы, обладающие такими возможностями.
22.5.2. Функторы в роли аргументов функций Другой способ передать функтор — Сделать это с помощью аргументов функции. Зто позволяет создавать необходимый объект-функцию во время работы программы (возможно, для этого придется применить нетривиальный конструктор). По своей эффективности способы передачи функтора в виде аргумента и в виде параметра шаблона почти одинаковы.
Различие состоит в том, что в первом случае обьект-функтор необходимо копировать. Обычно количество затрачиваемых при этом дополнительных ресурсов незначительно, а если обьект-функтор не содержит переменных-членов (часто именно так и бывает), это количество можно свести к нулю. Чтобы лучше понять сформулированное выше утверждение, рассмотрим модифицированную функцию щу вогт.
сетр1асе <куренное Р> чогс) ту вогс (..., Г сщр) ( 1б (сщр(х,у)) ( // Сравнение двух величин с // помощью объекта-функции // Вызов функции с функтором 1ау вогс (..., вМ::1евв<...>() ); В теле функции щу вогс ( ) мы имеем дело с копией передаваемого в нее объекта сщр. Если это значение представляет собой пустой объект класса, нет возможности отличить по состоянию локальный объект-функгор, сконструированный в самой функции, от перелаваемой в зту функцию копии.
Таким образом, вместо того чтобы передавать "пустой Функтор" с помощью аргумента функции, компилятор может просто использовать его лл" разрешения перегрузки, а затем избежать выделения памяти, необходимой 452 Глава 22. Объекты-функции и обратные вызовы для размещения параметра и аргумента. При этом внутри инстанцированной функции в роли функтора может выступать фиктивный локальный объект. Этот метод работает почти всегда, но при условии, что конструктор копирования "пустого функтора" лишен побочных эффектов.
На практике это означает, что любой функтор, в котором определен пользовательский конструктор копирования, не должен оптимизироваться таким образом. Как уже отмечалось, преимущество этого метода спецификации функтора состоит в том, что в аргументе функции можно передавать и указатель на обычную функцию. Ьоо1 ву сгйсегзопО (Т сопвсй х, Т сопвсй у) // Вызов функции с объектом-функцией ву вогг (..., ву сгйгегйоп); Кроме того, многие программисты просто предпочитают синтаксис вызова функции синтаксису, включающему аргументы шаблонов.
22.5.3. Сочетание параметров функции и параметров типа шаблона Путем комбинирования двух описанных выше методов передачи функгоров в функции и классы можно задавать аргументы функции, применяющиеся по умолчанию. севр1асе <Гурепаве Р> чотб ву вогс (..., р свр = Р() ) Тт (свр(х,у) ) ( // сравнение двух величин с // помощью объекта-функции Ьоо1 ву сгз.сегйоп ()(Т сопвсй х, Т сопвсй у) // Вызов функции с передачей функтора в аргументе шаблона ву ноге<вес)::1евв<...» (...); // Вызов функции с передачей функтора в ее аргументе ву вогс(..., вгй:гаева<...>()); // Вызов функции с указателем на функцию, // передаваемом в ее аргументе ву вогс(..., ву сгтгегтоп)з 22.5. Определение функторов 453 Таким образом созданы, например, классы упорядоченных множеств элементов, входящие в состав стандартной библиотеки С++.
при этом критерий сортировки можно передавать во время работы программы в виде аргумента конструктора. с1авв кцпе1шеСшр ( // Передача критерия сортировки на этапе компиляции в виде // аргумента шаблона (с использованием критерия сортировки, // который задается в конструкторе по умолчанию) век<хне,кцпгхшесшр> с1; // Передача критерия сортировки на этапе выполнения в виде // аргумента конструктора вег<хпс,кцпс1шеСшр> с2(кцпсхшеСшр(...))з Более подробную информацию по этому вопросу можно найти в [18].
22.5.4. Функторы в роли не являющихся типами аргументов шаблонов Еще один из возможных способов передачи функторов — через не являющиеся ти- пами аргументы шаблонов. Как уже упоминалось в разделах 4.3, стр. б2, и 8.3.3, стр. 133, объект функтора-класса (и вообще объект класса) не может выступать в роли не являю- щегося типом аргумента шаблона. Например, приведенный ниже код неверен. с1авв МуСг1сегхоп ( рцЬ11с: Ьоо1 орегасог() (ЯошеТуре соплей, ЯошеТуре соплей) сопле; гешр1аге <мусг1гегхоп Р> // ОшиБк)(: мусгзсег1оп// это класс Та шу вогс(...): Однако в роли не являющегося типом аргумента можно использовать указатель или ссылку на класс.
Это может привести к попытке создания кода, пример которого приведен ниже. с1авв МуСгтсег1оп ( рцЬ11с: ч1ггца1 Ьоо1 орегагог О (яошеТуре соплей, ЯошеТуре соплей) сопле с1авв Ьевятпап : рцвхйс МуСг1еегхоп ( Глава 22. Объекты-функции и обратные вызовы 454 риЬ11с: уйггиа1 Ьоо1 орегасог() (ЯощеЯЗре соплей, ЯощеТуре сопвсй) сопев; Сетр1аге<МуСгйсегйопй Р> уо3.с) вогс (...); ЬеввТЬап огс)ег; вогг<огс)ег> (...) // ОШИБКА: требуется привести // производный тип к базовому вогс<(МуСгйсегйопй)огдег> (...)г // ОШИБКА: аргумент, не являющийся // параметром типа, не должен быть // ссылкой с использованием // приведения типа.
Идея рассмотренного примера заключается в том, чтобы указать интерфейс критерия сортировки в абстрактном баювом классе, а затем использовать этот класс в качестве параметра шаблона В идеальном мире затем можно было бы просто создать производные классы (например, класс ЬевзТЬеп), с помощью которых был бы доступен интерфейс базового класса (мусгйсегйоп). к сожалению, в с++ такой подход недопустим, поскольку тип не являющихся типом аргументов шаблонов, содержащих ссылки или указатели, должен точно соответствовать типу параметра. Неявное преобразование производного типа к базовому не выполняется, а явное преобразование делает аргумент неприемлемым.
Анализ предыдущего примера показывает, что обьекты классов-функторов неудобно передавать с помощью не являющихся типами аргументов шаблонов. С другой стороны, в качестве таких аргументов вполне допуйтимо использовать указатели (и ссылки) на функции. В следующем разделе рассматриваются некоторые возможности, которые появляются при использовании этой концепции.