Функторы в С++. Начало знакомства

Функторы, что это такое?

  • Функторы (или функциональные объекты) — это любой объект, использование которого возможно подобно вызову функции.
В терминах С++ функции не являются объектами, поэтому они функторами не считаются. Вообще в программировании функции относят к частным случаям функторов, но не в языке С++. Хотя имена функций, не функции, а именно имена функций входят в категорию функциональных объектов. Такие вот существуют нюансы. Имя функции — это идентификатор, который умеет неявно приводиться к указателю; ситуация, как с именем массива. Поскольку указатели в терминах С++ объектами считаются, то и имя функции, приведённое к указателю, считается объектом, а поскольку оно объект, то можно говорить о функциональности объекта.
Посмотрим простейший пример использования функтора, когда в классе перегружается операция (), а потом объект класса используется подобно функции, вбирая в себя аргументы.


Как вы можете видеть, внутри main использование объекта sq уподоблено механизму вызова функции. Операция (), в отличие от всех остальных, не ограничивается числом разрешённых параметров, что довольно удобно и отчего позволительно объекту полноценно имитировать функции.
На вопрос "когда функциональные объекты полезны?" можно сказать, что они полезны тогда, когда функции должны вести себя как объекты. В С++ функторы занимают важное место в стандартной библиотеке шаблонов (в STL). Например, в алгоритмы сортировки нужно подсовывать некоторый критерий для поиска, а роль этого критерия берёт на себя функтор:

Куда вы ни ткнёте в STL, весьма врероятно, что вам потребуются функторы. Хотя функции из STL имеют перегруженные операции для приёма объектов классов, так и для указателей на функции, использовать объекты классов удобнее, потому что чем меньше указателей, тем обычно лучше.

  • Хотя любой указатель на функцию функтор, не любой функтор — указатель на функцию
Поскольку само понятие функтора включает в себя несколько разных сущностей, то понятие функтора чуть более широкое, чем если его ограничить одним из его составляющих. Иногда можно встретить мнение, что функторы заменяют указатели на функции, но это не так. Поскольку функтором может быть не только указатель на функцию, то иногда замена не выйдет. Один из таких примеров, это если некоторая функция в своих параметрах принимает указатель на функцию, а ей подсовывают объект класса:

Более понятна ситуация с терминологией, когда используют расширенные названия: функтор-объект_класса или функтор-указатель_на_функцию, но обычно эти длиные названия просто сокращаются до функтор, и в контексте чаще всего понятно, о каком виде функторов идёт речь.
Одна из наиболее выгодных полезностей функциональных-объектов_классов — умение хранить внутреннее состояние. Ведь внутри класса можно описать много независимых переменных и/или объектов. Это позволяет делать подобие настраиваемой функции. Есть люди, которые так говорят, но как это использовать, мне, например, очень сложно представить.
Поскольку в С++ функторы наиболее широко используются в стандартной библиотеке шаблонов, то и примеры в этой статье основаны на использовании этой библиотеки. Во многих случаях функторы представляют собой предикаты:

  • Предикат — это функция, которая возвращает булево значение
  • Предикатный класс — это класс, перегруженная операция () которого возвращает булево значение

Когда мы используем алгоритм сортировки из стандартной библиотеки шаблонов, то нам нужно задавать какой-то критерий, в каком случае делать перестановку, а в каком не делать. В зависимости от текущей на момент ситуации (от расстановки элементов) нам требуется только два ответа: надо выполнять перестановку или нет, для этого мы используем функцию, благодаря которой происходит анализ ситуации и отдаётся признак необходимости перестановки — если ответ true, то надо, а если false, то не надо. Предикат — это просто утверждение, в нашем случае утверждение о нужности или игноре выполнени какого-то действия. Таким образом, в листинге #2.1 функция compare предикат, потому что возвращает признак надобности в виде булева значения, а в листинге #2.2. перегруженная операция () предикат, потому что возвращает признак надобности в виде булева значения. Т. е. любая функция, которая возвращает значение с типом bool или используется как функция, возвращающая значение с типом bool — предикат. Но учтите, что не каждый предикат функтор, и не каждый функтор — предикат. Уже об этом говорилось, но повторюсь, обычная функция не объект и не считается функтором, но вполне может быть предикатом.

В стандартной библиотеке шаблонов предопределено много функторов, и описывать их иногда не имеет смысла. Так, например, мы могли использовать уже написанный до нас функтор для алгоритма сортировки, а не писать свой в листингах #2


В functional описаны функторы для сравнения, для арифметических операций, для логических операций и некоторые другие.

Хотя и существуют уже готовые функторы некоторых видов, их не всегда можно правильно использовать, и в таких случаях имеет смысл писать свои.
В С++11 были добавлены так называемые лямбда-функции. Это обёртки над функциональными объектами классов. Синтаксис их может казаться необычным, но они помогают сокращать количество строк кода. Поскольку лямбда-функции идентичны функторам-объектам_классов, то они такие же функторы, только имеют своеобразный синтаксис.


Такие лямбда-функции легко встраиваются в любое место, где нужен функтор, и даже могут использоваться просто как функции, но это уже немного другая тема, поэтому просто принимайте к сведению, что лямбда-функции — это функторы. Например, для алгоритма for_each мы могли бы не описывать отдельно функтор, а впаять непосредственно в место для функтора лямбда-функцию:

Позже вы научитесь их использовать, а пока, если не знаете, что это за зверь такой, не надо торопиться и лезть их учить. Вам достаточно знать того, что лямбда-функции — это функтор, такой же как функтор-объект_класса, только со своим собственным синтаксисом. А поскольку она объект, её можно присваивать в объекты и передавать в аргументах функциям и классам.
Итак, делаем некоторые выводы:

  • Функторы — это в С++ прежде всего классы с перегруженной операцией (), а потом любые объекты, которые умеют вести себя как функции: это указатели на функции, лямбда-функции и имена функций, но сами функции и ссылки на функции функторами не являются, потому что они в терминах С++ не объекты.
  • Функторы полезны там, где функции должны вести себя как объекты.
  • Функторы имеют очень важное значение при использовании стандартной библиотеки шаблонов (STL), а следовательно могут так же широко использоваться в других библиотеках.
  • В стандартной библиотеке шаблонов (STL) есть некоторое множество предопределённых функторов, и их нужно использовать в предпочтение своим самописным.
  • Функторы имеют свойсто быть пересылаемыми и присваиваемыми, поскольку они объекты; обычные функции таким свойством не обладают.
  • Функции, возвращающие булевы значения, называются предикатами. Функторы могут быть и очень часто являются предикатами.
При написании функторов нужно стараться делать их небольшими объектами и не стоит использовать виртуальные функции внутри них. В тех случаях, когда функторами-объектами_классов заменяются указатели на функции, оптимизаторы компиляторов умеют хорошо улучшать конечный код.
Конечно, тут есть ещё о чём написать, но это начало знакомства с функторами, и, надеюсь, то сказанное, что осветилось в этой теме, помогло вам или поспособствует пониманию и использованию их.

3 комментария на «“Функторы в С++. Начало знакомства”»

  1. Владимир:

    «Функторы имеют свойсто быть пересылаемыми и присваиваемыми, поскольку они объекты; обычные функции таким свойством не обладают.»
    Это почему указатели на функции не могут быть пересылаемыми и присваемыми?

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Поиск

 
     

Случайная книга в электронном формате

https://www.litres.ru/brayan-karper/programmirovanie-na-clojure-praktika-primeneniya-lisp-v-mire-java-10016554/?lfrom=15589587
Яндекс.Метрика