Я много слышу о функторах в C ++. Может ли кто-нибудь дать мне общее представление о том, кто они и в каких случаях они будут полезны?
876
Я много слышу о функторах в C ++. Может ли кто-нибудь дать мне общее представление о том, кто они и в каких случаях они будут полезны?
operator()(...)
значит: это перегрузка оператора «вызова функции» . Это просто перегрузка оператора для()
оператора. Не ошибитесьoperator()
с вызовом вызываемой функцииoperator
, но воспринимайте это как обычный синтаксис перегрузки операторов.Ответы:
Функтор - это просто класс, который определяет operator (). Это позволяет вам создавать объекты, которые «похожи» на функцию:
Есть несколько хороших вещей о функторах. Во-первых, в отличие от обычных функций, они могут содержать состояние. Приведенный выше пример создает функцию, которая добавляет 42 к тому, что вы ей даете. Но это значение 42 не является жестко заданным, оно было указано в качестве аргумента конструктора при создании нашего экземпляра функтора. Я мог бы создать еще один сумматор, который добавил 27, просто вызвав конструктор с другим значением. Это делает их красиво настраиваемыми.
Как показывают последние строки, вы часто передаете функторы в качестве аргументов другим функциям, таким как std :: transform или другим стандартным библиотечным алгоритмам. Вы можете сделать то же самое с обычным указателем на функцию, за исключением того, что, как я уже говорил выше, функторы можно «настраивать», поскольку они содержат состояние, что делает их более гибкими (если бы я хотел использовать указатель на функцию, мне пришлось бы написать функцию который добавил ровно 1. к своему аргументу. Функтор является общим и добавляет то, с чем вы его инициализировали), и они также потенциально более эффективны. В приведенном выше примере компилятор точно знает, какую функцию
std::transform
следует вызывать. Стоит позвонитьadd_x::operator()
. Это означает, что он может встроить этот вызов функции. И это делает его таким же эффективным, как если бы я вручную вызывал функцию для каждого значения вектора.Если бы я вместо этого передал указатель на функцию, компилятор не смог бы сразу увидеть, на какую функцию он указывает, поэтому, если он не выполнит некоторые довольно сложные глобальные оптимизации, ему придется разыменовывать указатель во время выполнения, а затем делать вызов.
источник
add42
, я бы использовал созданный ранее функтор и добавил 42 к каждому значению. С помощьюadd_x(1)
я создаю новый экземпляр функтора, который добавляет только 1 к каждому значению. Просто для того, чтобы показать, что часто вы создаете экземпляр функтора «на лету», когда вам это нужно, вместо того, чтобы сначала его создавать, и сохраняете его перед тем, как фактически использовать его для чего-либо.operator()
, потому что это то, что вызывающий использует для вызова. Что еще есть у функтора функций-членов, конструкторов, операторов и переменных-членов, полностью зависит от вас.add42
будет называться функтором, а неadd_x
(который является классом функтора или просто классом функтора). Я считаю эту терминологию непротиворечивой, потому что функторы также называются объектами функций , а не классами функций. Вы можете уточнить этот момент?Маленькое дополнение. Вы можете использовать
boost::function
для создания функторов из функций и методов, например так:и вы можете использовать boost :: bind, чтобы добавить состояние к этому функтору
и самое полезное, с boost :: bind и boost :: function вы можете создать функтор из метода класса, на самом деле это делегат:
Вы можете создать список или вектор функторов
Есть одна проблема со всем этим, сообщения об ошибках компилятора не читаются человеком :)
источник
operator ()
быть публично в вашем первом примере, так как классы по умолчанию закрыты?Функтор - это объект, который действует как функция. В основном, класс, который определяет
operator()
.Настоящее преимущество состоит в том, что функтор может удерживать состояние.
источник
int
когда должен вернутьсяbool
? Это C ++, а не C. Когда этот ответ был написан,bool
не существовало?Название «функтор» традиционно использовалось в теории категорий задолго до появления C ++. Это не имеет ничего общего с C ++ концепцией функтора. Лучше использовать объект функции name вместо того, что мы называем «функтором» в C ++. Так другие языки программирования называют подобные конструкции.
Используется вместо простой функции:
Особенности:
Минусы:
Используется вместо указателя функции:
Особенности:
Минусы:
Используется вместо виртуальной функции:
Особенности:
Минусы:
источник
foo(arguments)
. Следовательно, он может содержать переменные; например, если у вас былаupdate_password(string)
функция, вы можете отслеживать, как часто это происходило; с функтором, который можетprivate long time
представлять метку времени, когда это произошло в последний раз. С указателем на функцию или простой функцией вам нужно будет использовать переменную вне ее пространства имен, которая напрямую связана только с документацией и использованием, а не с определением.Как уже упоминалось, функтор - это объект, который действует как функция, т. Е. Перегружает оператор вызова функции.
Функторы обычно используются в алгоритмах STL. Они полезны, потому что они могут хранить состояние до и между вызовами функций, как замыкание в функциональных языках. Например, вы можете определить
MultiplyBy
функтор, который умножает свой аргумент на указанное количество:Затем вы можете передать
MultiplyBy
объект в алгоритм типа std :: transform:Другое преимущество функтора перед указателем на функцию заключается в том, что в большинстве случаев вызов может быть встроенным. Если вы передали указатель на функцию
transform
, если этот вызов не был встроен, и компилятор не знает, что вы всегда передаете ему одну и ту же функцию, он не сможет встроить вызов через указатель.источник
Для новичков, таких как я, среди нас: после небольшого исследования я выяснил, что сделал код, опубликованный jalf.
Функтор - это объект класса или структуры, который можно «вызвать» как функцию. Это стало возможным благодаря перегрузке
() operator
.() operator
(Не уверен , что его называют) может принимать любое количество аргументов. Другие операторы принимают только два, т. Е.+ operator
Могут принимать только два значения (по одному на каждой стороне оператора) и возвращать любое значение, для которого вы его перегрузили. Вы можете разместить любое количество аргументов внутри() operator
, что придает ему гибкость.Чтобы создать функтор, сначала вы создаете свой класс. Затем вы создаете конструктор для класса с параметром по вашему выбору типа и имени. В том же операторе следует список инициализатора (в котором используется один оператор двоеточия, что я также недавно знал), который создает объекты-члены класса с ранее объявленным параметром для конструктора. Затем
() operator
перегружается. Наконец, вы объявляете частные объекты созданного вами класса или структуры.Мой код (я нашел, что имена переменных jalf сбивают с толку)
Если что-то из этого является неточным или просто неправильным, не стесняйтесь исправлять меня!
источник
Функтор - это функция высшего порядка, которая применяет функцию к параметризованным (то есть шаблонным) типам. Это обобщение функции высшего порядка отображения . Например, мы могли бы определить функтор для
std::vector
этого:Эта функция принимает
std::vector<T>
и возвращает,std::vector<U>
когда ей передана функция,F
которая принимаетT
и возвращаетU
. Функтор не обязательно должен быть определен для типов контейнеров, он также может быть определен для любого шаблонного типа, включаяstd::shared_ptr
:Вот простой пример, который преобразует тип в
double
:Есть два закона, которым должны следовать функторы. Первый - это закон тождества, который гласит, что если функтору дана функция тождества, это должно быть то же самое, что применение функции тождества к типу, то есть
fmap(identity, x)
то же самое, что иidentity(x)
:Следующий закон - это закон композиции, который гласит, что если функтору дана композиция из двух функций, он должен быть таким же, как применение функтора для первой функции, а затем снова для второй функции. Итак,
fmap(std::bind(f, std::bind(g, _1)), x)
должно быть так же, какfmap(f, fmap(g, x))
:источник
fmap(id, x) = id(x)
иfmap(f ◦ g, x) = fmap(f, fmap(g, x))
.Вот реальная ситуация, когда я был вынужден использовать Functor для решения моей проблемы:
У меня есть набор функций (скажем, 20 из них), и все они идентичны, за исключением того, что каждая вызывает свою особую функцию в 3 конкретных местах.
Это невероятная трата и дублирование кода. Обычно я просто передаю указатель на функцию и вызываю ее в 3 точках. (Таким образом, код должен появляться только один раз, а не двадцать раз.)
Но потом я понял, что для каждой конкретной функции требуется совершенно другой профиль параметров! Иногда 2 параметра, иногда 5 параметров и т. Д.
Другое решение было бы иметь базовый класс, где конкретная функция является переопределенным методом в производном классе. Но действительно ли я хочу построить все это НАСЛЕДОВАНИЕ, просто чтобы я мог передать указатель на функцию ????
РЕШЕНИЕ: Итак, я сделал класс-оболочку («Functor»), который может вызывать любые функции, которые мне нужны. Я устанавливаю его заранее (с его параметрами и т. Д.), А затем передаю его вместо указателя функции. Теперь вызываемый код может запускать Functor, не зная, что происходит внутри. Он может даже звонить несколько раз (мне нужно было звонить 3 раза)
Вот и все - практический пример, когда Functor оказался очевидным и простым решением, которое позволило мне сократить дублирование кода с 20 функций до 1.
источник
За исключением использования в обратном вызове, функторы C ++ также могут помочь обеспечить стиль доступа Matlab, подобный классу матрицы . Есть пример .
источник
operator()
но не использование свойств объекта функции.Как уже было повторено, функторы - это классы, которые можно рассматривать как функции (оператор перегрузки ()).
Они наиболее полезны в ситуациях, когда вам необходимо связать некоторые данные с повторными или отложенными вызовами функции.
Например, связанный список функторов может быть использован для реализации базовой синхронной системы сопрограмм с минимальными издержками, диспетчера задач или прерываемого анализа файлов. Примеры:
Конечно, эти примеры сами по себе не так полезны. Они только показывают, как функторы могут быть полезны, сами функторы очень простые и негибкие, и это делает их менее полезными, чем, например, то, что обеспечивает повышение.
источник
Функторы используются в gtkmm для подключения некоторой кнопки GUI к реальной функции или методу C ++.
Если вы используете библиотеку pthread, чтобы сделать ваше приложение многопоточным, Functors могут вам помочь.
Чтобы запустить поток, одним из аргументов
pthread_create(..)
является указатель функции, который должен быть выполнен в его собственном потоке.Но есть одно неудобство. Этот указатель не может быть указателем на метод, если это не статический метод или если вы не укажете его класс , например
class::method
. И еще, интерфейс вашего метода может быть только:Таким образом, вы не можете запускать (простым и понятным способом) методы из вашего класса в потоке, не делая ничего лишнего.
Очень хороший способ работы с потоками в C ++ - это создание собственного
Thread
класса. Если вы хотите запускать методы изMyClass
класса, то, что я сделал, трансформируйте эти методы вFunctor
производные классы.Кроме того, у
Thread
класса есть этот метод:static void* startThread(void* arg)
указатель на этот метод будет использоваться в качестве аргумента для вызова
pthread_create(..)
. И чтоstartThread(..)
должно получить в arg - этоvoid*
приведенная ссылка на экземпляр в куче любогоFunctor
производного класса, который будет приведен обратноFunctor*
при выполнении, а затем вызван егоrun()
методом.источник
Чтобы добавить, я использовал функциональные объекты для подгонки существующего унаследованного метода к шаблону команды; (единственное место, где красота ОО-парадигмы истинного ОЗП я ощутил); Также добавляем сюда шаблон адаптера связанной функции.
Предположим, у вашего метода есть подпись:
Мы увидим, как мы можем подогнать его под шаблон Command - для этого сначала нужно написать адаптер функции-члена, чтобы он мог вызываться как объект функции.
Обратите внимание - это ужасно, и, возможно, вы можете использовать помощников Boost bind и т. Д., Но если вы не можете или не хотите, это один из способов.
Кроме того, нам нужен вспомогательный метод mem_fun3 для вышеуказанного класса, чтобы помочь в вызове.
}
Теперь, чтобы связать параметры, мы должны написать функцию связывания. Итак, вот оно:
И вспомогательная функция для использования класса binder3 - bind3:
Теперь мы должны использовать это с классом Command; используйте следующую typedef:
Вот как вы это называете:
Примечание: f3 (); вызовет метод task1-> ThreeParameterTask (21,22,23) ;.
Полный контекст этого шаблона по следующей ссылке
источник
Большим преимуществом реализации функций как функторов является то, что они могут поддерживать и повторно использовать состояние между вызовами. Например, многие алгоритмы динамического программирования, такие как алгоритм Вагнера-Фишера для вычисления расстояния Левенштейна между строками, работают, заполняя большую таблицу результатов. Распределять эту таблицу при каждом вызове функции крайне неэффективно, поэтому реализация функции в качестве функтора и превращение таблицы в переменную-член может значительно повысить производительность.
Ниже приведен пример реализации алгоритма Вагнера-Фишера в качестве функтора. Обратите внимание, как таблица размещается в конструкторе, а затем используется повторно
operator()
с изменением размера по мере необходимости.источник
Функтор также можно использовать для имитации определения локальной функции внутри функции. Обратитесь к вопросу и другому .
Но локальный функтор не может получить доступ к внешним авто переменным. Функция лямбда (C ++ 11) является лучшим решением.
источник
Я «открыл» очень интересное использование функторов: я использую их, когда у меня нет подходящего имени для одного метода, поскольку функтор - это метод без имени ;-)
источник