Что такое функторы C++ и их использование?

Я много слышу о функторах в C++. Может ли кто-нибудь дать мне обзор того, что они из себя представляют и в каких случаях они могут быть полезны?

Эта тема была затронута в ответе на этот вопрос: stackoverflow.com/questions/317450/why-override-operator#317‌ 528

Luc Touraille 10.12.2008 21:00

Он используется для создания замыкания в C++.

copper.hat 30.06.2019 07:13

Глядя на ответы ниже, если кому-то интересно, что означает operator()(...): он перегружает оператор "вызов функции". Это просто перегрузка оператора для оператора (). Не путайте operator() с вызовом функции operator, но рассматривайте это как обычный синтаксис перегрузки оператора.

zardosht 26.05.2020 16:22
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
935
3
496 879
14
Перейти к ответу Данный вопрос помечен как решенный

Ответы 14

Функтор - это объект, который действует как функция. По сути, класс, который определяет operator().

class MyFunctor
{
   public:
     int operator()(int x) { return x * 2;}
}

MyFunctor doubler;
int x = doubler(5);

Настоящее преимущество состоит в том, что функтор может удерживать состояние.

class Matcher
{
   int target;
   public:
     Matcher(int m) : target(m) {}
     bool operator()(int x) { return x == target;}
}

Matcher Is5(5);

if (Is5(n))    // same as if (n == 5)
{ ....}

Просто нужно добавить, что их можно использовать как указатель на функцию.

Martin York 10.12.2008 21:04

@LokiAstari - для тех, кто плохо знаком с этой концепцией, это может ввести в заблуждение. Функторы можно использовать «как», но не всегда «вместо» указателей на функции. Например, функция, которая принимает указатель на функцию, не может принимать функтор на свое место, даже если функтор имеет те же аргументы и возвращаемое значение, что и указатель функции. Но в целом при проектировании функторы являются предпочтительным и теоретически «более современным» способом.

MasonWinsauer 13.03.2014 01:49

Почему второй возвращает int, а должен возвращать bool? Это C++, а не C. Когда был написан этот ответ, bool не существовал?

Fund Monica's Lawsuit 08.05.2016 20:40

@QPaysTaxes Думаю, опечатка. Я, наверное, скопировал код из первого примера и забыл его изменить. Я исправил это сейчас.

James Curran 10.05.2016 21:09

О, в этом есть смысл. Спасибо за терпение с моей педантичностью :)

Fund Monica's Lawsuit 10.05.2016 21:15

В чем преимущество использования логического типа Is5 (int n) {return 5 == n;}

StarDust 29.09.2017 23:16

@Riasat Если Matcher находится в библиотеке, определить Is5 () довольно просто. И вы можете создать Is7 (), Is32 () и т.д. Кроме того, это просто пример. Функтор мог бы быть намного сложнее.

James Curran 30.09.2017 05:57
Ответ принят как подходящий

Функтор - это в значительной степени просто класс, определяющий оператор (). Это позволяет вам создавать объекты, которые «выглядят» как функция:

// this is a functor
struct add_x {
  add_x(int val) : x(val) {}  // Constructor
  int operator()(int y) const { return x + y; }

private:
  int x;
};

// Now you can use it like this:
add_x add42(42); // create an instance of the functor class
int i = add42(8); // and "call" it
assert(i == 50); // and it added 42 to its argument

std::vector<int> in; // assume this contains a bunch of values)
std::vector<int> out(in.size());
// Pass a functor to std::transform, which calls the functor on every element 
// in the input sequence, and stores the result to the output sequence
std::transform(in.begin(), in.end(), out.begin(), add_x(1)); 
assert(out[i] == in[i] + 1); // for all i

В функторах есть несколько приятных моментов. Во-первых, в отличие от обычных функций, они могут содержать состояние. В приведенном выше примере создается функция, которая добавляет 42 к тому, что вы ей даете. Но это значение 42 не жестко запрограммировано, оно было указано в качестве аргумента конструктора при создании нашего экземпляра функтора. Я мог бы создать еще один сумматор, который добавил бы 27, просто вызвав конструктор с другим значением. Это делает их легко настраиваемыми.

Как видно из последних строк, вы часто передаете функторы в качестве аргументов другим функциям, таким как std :: transform или другим стандартным библиотечным алгоритмам. Вы можете сделать то же самое с обычным указателем на функцию, за исключением того, что, как я сказал выше, функторы можно «настроить», потому что они содержат состояние, что делает их более гибкими (если бы я хотел использовать указатель на функцию, мне пришлось бы написать функцию который добавил к своему аргументу ровно 1. Функтор является общим и добавляет все, чем вы его инициализировали), и они также потенциально более эффективны. В приведенном выше примере компилятор точно знает, какую функцию std::transform должен вызывать. Он должен вызвать add_x::operator(). Это означает, что он может встроить этот вызов функции. И это делает его столь же эффективным, как если бы я вручную вызвал функцию для каждого значения вектора.

Если бы вместо этого я передал указатель на функцию, компилятор не смог бы сразу увидеть, на какую функцию он указывает, поэтому, если он не выполняет довольно сложные глобальные оптимизации, ему пришлось бы разыменовать указатель во время выполнения, а затем выполнить вызов.

Не могли бы вы объяснить эту строку, пожалуйста, std :: transform (in.begin (), in.end (), out.begin (), add_x (1)); почему вы пишете там add_x, а не add42?

Alecs 12.09.2011 18:48

@Alecs Оба сработали бы (но эффект был бы другим). Если бы я использовал add42, я бы использовал созданный ранее функтор и добавил 42 к каждому значению. С помощью add_x(1) я создаю новый экземпляр функтора, который добавляет только 1 к каждому значению. Это просто для того, чтобы показать, что часто вы создаете экземпляр функтора «на лету», когда он вам нужен, а не создаете его сначала и храните, прежде чем фактически использовать его для чего-либо.

jalf 12.09.2011 19:12

Могут ли функторы иметь и другие функции-члены?

zar 08.02.2014 09:43

@задане конечно. У них просто должен быть operator(), потому что это то, что вызывающий использует для его вызова. Какие функции-члены, конструкторы, операторы и переменные-члены будут иметь еще у функтора, полностью зависит от вас.

jalf 09.02.2014 16:37
std::transform(in.begin(), in.end(), out.begin(), [](int &i){ i+=1;});
caub 16.02.2014 02:34

@crl Разве это не должно быть std::transform(in.begin(), in.end(), out.begin(), [](int i) { return i + 1; });?

Mateen Ulhaq 05.09.2015 15:58

«в отличие от обычных функций, они могут содержать состояние» <- ммм, обычные функции могут иметь статические переменные, поэтому - они могут содержать состояние, хотя семантика немного хитрая.

einpoklum 25.01.2016 00:20

Функция @jalf class which defines the operator() не является классом, но все же является функтором.

rikimaru2013 24.02.2016 14:08

@ rikimaru2013 На языке функционального программирования вы правы, функция также является функтором, но на языке C++ функтор - это, в частности, класс, используемый как функция. Раньше терминологией злоупотребляли, но это разделение - полезное различие, и оно сохраняется сегодня. Если вы начнете называть функции «функторами» в контексте C++, вы просто запутаете разговор.

srm 29.03.2016 01:09

Это класс или экземпляр класса? В большинстве источников функтором будет называться add42, а не add_x (который является классом функтора или просто классом функтора). Я считаю эту терминологию последовательной, потому что функторы также называются функциональные объекты, а не функциональными классами. Вы можете прояснить этот момент?

Sergei Tachenov 13.06.2016 17:41

так что это как в javascript, когда у вас есть function makeadd(bynumber){ return function(x){ return x + bynumber; }}

Omu 25.11.2016 23:45

Некоторое время меня озадачивало то, что вы назвали std::transform функцией. Это не так, это шаблон, который выглядит как функция в вашем коде благодаря вычету параметров шаблона.

Euri Pinhollow 16.01.2017 15:50

Я люблю, когда люди не только что-то объясняют, но и мотивируют это использовать. Спасибо.

JoeManiaci 04.04.2017 20:49

Функции @einpoklum могут содержать только одно состояние, если вы также не передаете состояние (само состояние или какое-либо значение, идентифицирующее состояние) в качестве аргумента.

Euri Pinhollow 27.11.2017 20:55

Ответ хорош, но в нем отсутствуют некоторые важные данные, которые очевидны для опытных программистов, но не особенно для новичков: 1) только шаблоны функций имеют то преимущество, что компилятору становится очевидным, какая функция была использована. 2) Если функция принимает std::function<whatever(whatever)>, то преимущество встраивания теряется. --------- Когда я впервые прочитал этот ответ много лет назад, он оставил меня в состоянии ошибки из-за вывода аргументов, который скрыл тот факт, что вы не можете передать какой-либо объект не-шаблонной функции.

Euri Pinhollow 27.11.2017 21:03

@EuriPinhollow при передаче аргументов, например std::transform(in.begin(), in.end(), out.begin(), add_x(1)), у вас есть вызов функции. Вы правы, что std::transformсам по себе - это не функция.

Caleth 06.03.2018 18:40

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

Euri Pinhollow 10.03.2018 00:28

Это верно. Но с новым C++ 11 функторы lambdas в значительной степени мертвы. Теперь вам может не понадобиться создавать целый класс для функции, вместо этого функция должна быть встроена в вызов метода. Лямбды одинаково эффективны как с точки зрения использования, так и с точки зрения производительности.

Sisir 12.02.2019 17:37

@Sisir, когда у вас только один operator(), конечно. Если у вас есть несколько разных состояний совместного использования operator(), то несколько лямбд сложнее получить, чем явный класс закрытия.

Caleth 02.07.2019 13:10

Что делает std::transform(in.begin(), in.end(), out.begin(), add_x(1)); на первой итерации, пожалуйста?

Franky 10.04.2020 22:56

Меня особенно интересует имя функтор, имеет ли оно какое-либо отношение к функторам в теории кошек? Я только что услышал это слово от некоторых друзей, и я ничего о нем не знаю, и меня это направление не интересует - просто любопытно.

Ray Eldath 26.07.2020 19:51

Как уже упоминалось, функтор - это объект, который действует как функция, то есть перегружает оператор вызова функции.

Функторы обычно используются в алгоритмах STL. Они полезны, потому что могут удерживать состояние до и между вызовами функций, как закрытие в функциональных языках. Например, вы можете определить функтор MultiplyBy, который умножает свой аргумент на указанную величину:

class MultiplyBy {
private:
    int factor;

public:
    MultiplyBy(int x) : factor(x) {
    }

    int operator () (int other) const {
        return factor * other;
    }
};

Затем вы можете передать объект MultiplyBy алгоритму вроде std :: transform:

int array[5] = {1, 2, 3, 4, 5};
std::transform(array, array + 5, array, MultiplyBy(3));
// Now, array is {3, 6, 9, 12, 15}

Еще одно преимущество функтора перед указателем на функцию состоит в том, что вызов может быть встроен в большем количестве случаев. Если вы передали указатель функции на transform, если только вызов который не был встроен и компилятор не знает, что вы всегда передаете ему одну и ту же функцию, он не сможет встроить вызов через указатель.

Я искал именно этот пример, потому что только что видел его на курсе C++ и не понимал его. Обычно мы определяем объект класса, который реализует operator (), и передаем его как аргумент, функтор, такой функции, как transform. Однако в этом случае мы просто создаем объект в том же вызове. Это единственная разница? Что функтор выходит за пределы области видимости и уничтожается после завершения transform? Спасибо!

rturrado 16.02.2021 17:04

@rturrado Да, это правильно.

Matthew Crumley 19.02.2021 02:55

Небольшое дополнение. Вы можете использовать boost::function для создания функторов из функций и методов, например:

class Foo
{
public:
    void operator () (int i) { printf("Foo %d", i); }
};
void Bar(int i) { printf("Bar %d", i); }
Foo foo;
boost::function<void (int)> f(foo);//wrap functor
f(1);//prints "Foo 1"
boost::function<void (int)> b(&Bar);//wrap normal function
b(1);//prints "Bar 1"

и вы можете использовать boost :: bind, чтобы добавить состояние к этому функтору

boost::function<void ()> f1 = boost::bind(foo, 2);
f1();//no more argument, function argument stored in f1
//and this print "Foo 2" (:
//and normal function
boost::function<void ()> b1 = boost::bind(&Bar, 2);
b1();// print "Bar 2"

и самое полезное, с помощью boost :: bind и boost :: function вы можете создать функтор из метода класса, на самом деле это делегат:

class SomeClass
{
    std::string state_;
public:
    SomeClass(const char* s) : state_(s) {}

    void method( std::string param )
    {
        std::cout << state_ << param << std::endl;
    }
};
SomeClass *inst = new SomeClass("Hi, i am ");
boost::function< void (std::string) > callback;
callback = boost::bind(&SomeClass::method, inst, _1);//create delegate
//_1 is a placeholder it holds plase for parameter
callback("useless");//prints "Hi, i am useless"

Вы можете создать список или вектор функторов

std::list< boost::function<void (EventArg e)> > events;
//add some events
....
//call them
std::for_each(
        events.begin(), events.end(), 
        boost::bind( boost::apply<void>(), _1, e));

У всего этого есть одна проблема: сообщения об ошибках компилятора не читаются человеком :)

Разве operator () не должен быть общедоступным в вашем первом примере, поскольку классы по умолчанию являются частными?

NathanOliver 16.06.2015 16:43

возможно, в какой-то момент этот ответ заслуживает обновления, поскольку теперь лямбды - это самый простой способ получить функтор из чего-либо

largest_prime_is_463035818 30.01.2019 21:29

int C++ 11 есть std::function и std::bind

phuclv 25.10.2020 12:26

Имя «функтор» традиционно использовалось в теория категорий задолго до появления C++. Это не имеет ничего общего с концепцией функтора C++. Лучше использовать имя функциональный объект вместо того, что мы называем «функтором» в C++. Так в других языках программирования называют подобные конструкции.

Используется вместо простой функции:

Функции:

  • Функциональный объект может иметь состояние
  • Функциональный объект вписывается в ООП (ведет себя как любой другой объект).

Минусы:

  • Делает программу более сложной.

Используется вместо указателя функции:

Функции:

  • Функциональный объект часто может быть встроен

Минусы:

  • Функциональный объект не может быть заменен другим типом функционального объекта во время выполнения (по крайней мере, если он не расширяет некоторый базовый класс, что, следовательно, дает некоторые накладные расходы)

Используется вместо виртуальной функции:

Функции:

  • Функциональный объект (не виртуальный) не требует диспетчеризации vtable и времени выполнения, поэтому в большинстве случаев он более эффективен.

Минусы:

  • Функциональный объект не может быть заменен другим типом функционального объекта во время выполнения (по крайней мере, если он не расширяет некоторый базовый класс, что, следовательно, дает некоторые накладные расходы)

Можете ли вы объяснить эти варианты использования на реальном примере? как мы можем использовать функторы в качестве указателя на функцию и полиморфизм?

Milad Khajavi 21.02.2013 21:40

Что на самом деле означает, что функтор сохраняет состояние?

erogol 15.04.2013 16:05

спасибо, что указали на то, что нужен базовый класс для какого-то полиморфизма. У меня просто проблема в том, что я должен использовать функтор в том же месте, что и простой указатель на функцию, и единственный способ, который я нашел, - это написать базовый класс функтора (поскольку я не могу использовать материал C++ 11). Не был уверен, имеет ли смысл эти накладные расходы, пока не прочитал ваш ответ.

largest_prime_is_463035818 09.01.2015 15:36

@Erogol Функтор - это объект, который поддерживает синтаксис foo(arguments). Следовательно, он может содержать переменные; например, если у вас есть функция update_password(string), вы можете отслеживать, как часто это происходило; с функтором, которым может быть private long time, представляющий отметку времени, когда это произошло в последний раз. С указателем на функцию или простой функцией вам нужно будет использовать переменную вне ее пространства имен, которое напрямую связано только с документацией и использованием, а не по определению.

Fund Monica's Lawsuit 09.05.2016 20:10

⁺¹ за упоминание о том, что название было придумано без причины. Я только что искал, какая связь между математическим функтором (или функциональный, если хотите) и функтором из C++.

Hi-Angel 15.10.2017 21:25

Спасибо за подробное описание вводящего в заблуждение использования слова "функтор".

copper.hat 10.02.2019 10:55

Функторы используются в gtkmm для подключения некоторой кнопки графического интерфейса пользователя к реальной функции или методу C++.


Если вы используете библиотеку pthread для создания многопоточного приложения, функторы могут вам в этом помочь. Для запуска потока одним из аргументов pthread_create(..) является указатель функции, которая должна выполняться в его собственном потоке. Но есть одно неудобство. Этот указатель не может быть указателем на метод, если это не статический метод, или если вы не указать его класс, например class::method. И еще, интерфейс вашего метода может быть только:

void* method(void* something)

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

Очень хороший способ работы с потоками в C++ - создать собственный класс Thread. Если вы хотите запускать методы из класса MyClass, я преобразовал эти методы в классы, производные от Functor.

Также в классе Thread есть такой метод: static void* startThread(void* arg)
Указатель на этот метод будет использоваться в качестве аргумента для вызова pthread_create(..). И то, что startThread(..) должен получить в arg, - это приведенная ссылка void* на экземпляр в куче любого производного класса Functor, который будет возвращен обратно в Functor* при выполнении, а затем вызван его методом run().

Есть пример?

OS2 16.08.2020 23:32

Вот реальная ситуация, когда я был вынужден использовать функтор для решения моей проблемы:

У меня есть набор функций (скажем, 20 из них), и все они идентичны, за исключением того, что каждая вызывает другую конкретную функцию в 3 конкретных местах.

Это невероятная трата и дублирование кода. Обычно я просто передаю указатель на функцию и вызываю его в трех местах. (Таким образом, код должен появляться только один раз, а не двадцать раз.)

Но потом я понял, что в каждом случае для конкретной функции требуется совершенно другой профиль параметров! Иногда 2 параметра, иногда 5 параметров и т. д.

Другое решение - иметь базовый класс, в котором конкретная функция является переопределенным методом в производном классе. Но действительно ли я хочу построить все это НАСЛЕДОВАНИЕ, чтобы я мог передать указатель на функцию ????

РЕШЕНИЕ: Итак, что я сделал, я создал класс-оболочку («Функтор»), который может вызывать любую из вызываемых мне функций. Я настраиваю его заранее (с его параметрами и т. д.), А затем передаю его вместо указателя на функцию. Теперь вызываемый код может запускать Functor, не зная, что происходит внутри. Он даже может вызывать его несколько раз (мне нужно было, чтобы он звонил 3 раза).


Вот и все - практический пример, когда Functor оказался очевидным и простым решением, которое позволило мне сократить дублирование кода с 20 функций до 1.

Если ваш функтор вызывал разные конкретные функции, и эти другие функции различались по количеству принимаемых параметров, означает ли это, что ваш функтор принял переменное количество аргументов для отправки этим другим функциям?

johnbakers 28.02.2014 10:09

не могли бы вы объяснить приведенный выше сценарий, процитировав некоторую часть кода, я новичок в C++, хочу понять эту концепцию.

sanjeev 05.09.2017 13:13

За исключением использования в обратном вызове, функторы C++ также могут помочь предоставить стиль доступа Matlab, соответствующий классу матрица. Есть пример.

Это (пример матрицы) простое использование operator(), но без использования свойств объекта функции.

renardesque 04.07.2019 12:40

Для новичков, таких как я, среди нас: после небольшого исследования я понял, что сделал код, опубликованный jalf.

Функтор - это класс или объект структуры, который можно «вызывать» как функцию. Это стало возможным благодаря перегрузке () operator. () operator (не уверен, как он называется) может принимать любое количество аргументов. Другие операторы принимают только два, т.е. + operator может принимать только два значения (по одному с каждой стороны оператора) и возвращать то значение, для которого вы его перегрузили. Вы можете разместить любое количество аргументов внутри () operator, что придает ему гибкость.

Чтобы создать функтор, сначала вы создаете свой класс. Затем вы создаете конструктор для класса с параметром по вашему выбору типа и имени. За этим в том же операторе следует список инициализаторов (который использует единственный оператор двоеточия, в котором я тоже был новичком), который конструирует объекты-члены класса с ранее объявленным параметром конструктора. Тогда () operator перегружен. Наконец, вы объявляете частные объекты созданного вами класса или структуры.

Мой код (имена переменных jalf меня сбивают с толку)

class myFunctor
{ 
    public:
        /* myFunctor is the constructor. parameterVar is the parameter passed to
           the constructor. : is the initializer list operator. myObject is the
           private member object of the myFunctor class. parameterVar is passed
           to the () operator which takes it and adds it to myObject in the
           overloaded () operator function. */
        myFunctor (int parameterVar) : myObject( parameterVar ) {}

        /* the "operator" word is a keyword which indicates this function is an 
           overloaded operator function. The () following this just tells the
           compiler that () is the operator being overloaded. Following that is
           the parameter for the overloaded operator. This parameter is actually
           the argument "parameterVar" passed by the constructor we just wrote.
           The last part of this statement is the overloaded operators body
           which adds the parameter passed to the member object. */
        int operator() (int myArgument) { return myObject + myArgument; }

    private: 
        int myObject; //Our private member object.
}; 

Если что-то из этого неточно или просто неправильно, не стесняйтесь поправлять меня!

Оператор () называется оператором вызова функции. Я думаю, вы также можете назвать это оператором круглых скобок.

Gautam 13.10.2013 00:19
"Этот параметр на самом деле является аргументом" parameterVar ", переданным только что написанным конструктором" А?
Lightness Races in Orbit 08.09.2015 15:04

Чтобы добавить к этому, я использовал объекты функций, чтобы подогнать существующий устаревший метод к шаблону команды; (единственное место, где я ощутил всю красоту ОО-парадигмы истинной OCP); Также добавляем сюда связанный шаблон адаптера функции.

Предположим, у вашего метода есть подпись:

int CTask::ThreeParameterTask(int par1, int par2, int par3)

Мы увидим, как мы можем приспособить его для шаблона Command - для этого сначала вам нужно написать адаптер функции-члена, чтобы его можно было вызывать как объект функции.

Примечание - это уродливо, и, возможно, вы можете использовать помощники привязки Boost и т. д., Но если вы не можете или не хотите, это один из способов.

// a template class for converting a member function of the type int        function(int,int,int)
//to be called as a function object
template<typename _Ret,typename _Class,typename _arg1,typename _arg2,typename _arg3>
class mem_fun3_t
{
  public:
explicit mem_fun3_t(_Ret (_Class::*_Pm)(_arg1,_arg2,_arg3))
    :m_Ptr(_Pm) //okay here we store the member function pointer for later use
    {}

//this operator call comes from the bind method
_Ret operator()(_Class *_P, _arg1 arg1, _arg2 arg2, _arg3 arg3) const
{
    return ((_P->*m_Ptr)(arg1,arg2,arg3));
}
private:
_Ret (_Class::*m_Ptr)(_arg1,_arg2,_arg3);// method pointer signature
};

Кроме того, нам нужен вспомогательный метод mem_fun3 для вышеуказанного класса, чтобы помочь в вызове.

template<typename _Ret,typename _Class,typename _arg1,typename _arg2,typename _arg3>
mem_fun3_t<_Ret,_Class,_arg1,_arg2,_arg3> mem_fun3 ( _Ret (_Class::*_Pm)          (_arg1,_arg2,_arg3) )
{
  return (mem_fun3_t<_Ret,_Class,_arg1,_arg2,_arg3>(_Pm));
}

Теперь, чтобы связать параметры, нам нужно написать функцию связывания. Итак, вот оно:

template<typename _Func,typename _Ptr,typename _arg1,typename _arg2,typename _arg3>
class binder3
{
public:
//This is the constructor that does the binding part
binder3(_Func fn,_Ptr ptr,_arg1 i,_arg2 j,_arg3 k)
    :m_ptr(ptr),m_fn(fn),m1(i),m2(j),m3(k){}

 //and this is the function object 
 void operator()() const
 {
        m_fn(m_ptr,m1,m2,m3);//that calls the operator
    }
private:
    _Ptr m_ptr;
    _Func m_fn;
    _arg1 m1; _arg2 m2; _arg3 m3;
};

И вспомогательная функция для использования класса binder3 - bind3:

//a helper function to call binder3
template <typename _Func, typename _P1,typename _arg1,typename _arg2,typename _arg3>
binder3<_Func, _P1, _arg1, _arg2, _arg3> bind3(_Func func, _P1 p1,_arg1 i,_arg2 j,_arg3 k)
{
    return binder3<_Func, _P1, _arg1, _arg2, _arg3> (func, p1,i,j,k);
}

Теперь мы должны использовать это с классом Command; используйте следующий typedef:

typedef binder3<mem_fun3_t<int,T,int,int,int> ,T* ,int,int,int> F3;
//and change the signature of the ctor
//just to illustrate the usage with a method signature taking more than one parameter
explicit Command(T* pObj,F3* p_method,long timeout,const char* key,
long priority = PRIO_NORMAL ):
m_objptr(pObj),m_timeout(timeout),m_key(key),m_value(priority),method1(0),method0(0),
method(0)
{
    method3 = p_method;
}

Вот как вы это называете:

F3 f3 = PluginThreadPool::bind3( PluginThreadPool::mem_fun3( 
      &CTask::ThreeParameterTask), task1,2122,23 );

Примечание: f3(); вызовет метод task1->ThreeParameterTask(21,22,23);.

Полный контекст этого шаблона в следующем связь

Функтор - это функция высшего порядка, который применяет функцию к параметризованным (т. Е. Шаблонным) типам. Это обобщение функции высшего порядка карта. Например, мы могли бы определить функтор для std::vector следующим образом:

template<class F, class T, class U=decltype(std::declval<F>()(std::declval<T>()))>
std::vector<U> fmap(F f, const std::vector<T>& vec)
{
    std::vector<U> result;
    std::transform(vec.begin(), vec.end(), std::back_inserter(result), f);
    return result;
}

Эта функция принимает std::vector<T> и возвращает std::vector<U>, когда ей дана функция F, которая принимает T и возвращает U. Функтор не обязательно должен определяться над типами контейнеров, он также может быть определен для любого шаблонного типа, включая std::shared_ptr:

template<class F, class T, class U=decltype(std::declval<F>()(std::declval<T>()))>
std::shared_ptr<U> fmap(F f, const std::shared_ptr<T>& p)
{
    if (p == nullptr) return nullptr;
    else return std::shared_ptr<U>(new U(f(*p)));
}

Вот простой пример преобразования типа в double:

double to_double(int x)
{
    return x;
}

std::shared_ptr<int> i(new int(3));
std::shared_ptr<double> d = fmap(to_double, i);

std::vector<int> is = { 1, 2, 3 };
std::vector<double> ds = fmap(to_double, is);

Функторы должны следовать двум законам. Первый - это закон идентичности, который гласит, что если функтору дана функция идентичности, она должна быть такой же, как применение функции идентичности к типу, то есть fmap(identity, x) должен быть таким же, как identity(x):

struct identity_f
{
    template<class T>
    T operator()(T x) const
    {
        return x;
    }
};
identity_f identity = {};

std::vector<int> is = { 1, 2, 3 };
// These two statements should be equivalent.
// is1 should equal is2
std::vector<int> is1 = fmap(identity, is);
std::vector<int> is2 = identity(is);

Следующий закон - это закон композиции, который гласит, что если функтору дана композиция из двух функций, она должна быть такой же, как применение функтора для первой функции, а затем снова для второй функции. Итак, fmap(std::bind(f, std::bind(g, _1)), x) должен быть таким же, как fmap(f, fmap(g, x)):

double to_double(int x)
{
    return x;
}

struct foo
{
    double x;
};

foo to_foo(double x)
{
    foo r;
    r.x = x;
    return r;
}

std::vector<int> is = { 1, 2, 3 };
// These two statements should be equivalent.
// is1 should equal is2
std::vector<foo> is1 = fmap(std::bind(to_foo, std::bind(to_double, _1)), is);
std::vector<foo> is2 = fmap(to_foo, fmap(to_double, is));

В статье утверждается, что для этого значения следует правильно использовать функтор (см. Также en.wikipedia.org/wiki/Functor), и что его использование для объектов функций просто небрежно: jackieokay.com/2017/01/26/functors.html Для этого может быть слишком поздно, учитывая количество ответов здесь, которые рассматривают только объект функции имея в виду.

armb 12.02.2017 16:33

Этот ответ должен быть тем, у кого> 700 голосов за. Поскольку кто-то знает Haskell лучше, чем C++, язык C++ постоянно озадачивал меня.

mschmidt 27.12.2017 00:34

Теория категорий и C++? Это секретный аккаунт SO Бартоша Милевски?

Mateen Ulhaq 08.08.2018 02:35

Было бы полезно резюмировать законы функторов в стандартных обозначениях: fmap(id, x) = id(x) и fmap(f ◦ g, x) = fmap(f, fmap(g, x)).

Mateen Ulhaq 08.08.2018 02:40

@mschmidt, хотя функтор также означает это, C++ перегружает имя так, чтобы оно означало то же, что и "объект функции"

Caleth 02.07.2019 13:16

Функтор не упоминается в стандарте C++. cppreference.com не дает определения функтора, но дает определение FunctionObject без упоминания функтора вообще.

Paul Fultz II 02.07.2019 19:46

С ++ и математическая терминология расходятся во многих областях. Это ни нежелательно, ни неправильно. Например. "вектор". Математические понятия, такие как «массив», «последовательность», «матрица» или «вектор», не имеют эквивалента в программировании, потому что для программирования требуется конкретный тип данных, который математика совершенно не касается. В математике можно говорить о последовательности чисел, но в программировании это бессмысленно. Вы говорите о списке? вектор? дек? То, что некоторые из этих терминов дублируют математические, - это нормально. Даже «функция» не имеет математического значения, поэтому объект функции не работает.

Exaeta 05.09.2019 20:30

Как уже говорилось, функторы - это классы, которые можно рассматривать как функции (оператор перегрузки ()).

Они наиболее полезны в ситуациях, когда вам нужно связать некоторые данные с повторяющимися или отложенными вызовами функции.

Например, связанный список функторов может использоваться для реализации базовой системы синхронных сопрограмм с низкими накладными расходами, диспетчера задач или прерываемого синтаксического анализа файлов. Примеры:

/* prints "this is a very simple and poorly used task queue" */
class Functor
{
public:
    std::string output;
    Functor(const std::string& out): output(out){}
    operator()() const
    {
        std::cout << output << " ";
    }
};

int main(int argc, char **argv)
{
    std::list<Functor> taskQueue;
    taskQueue.push_back(Functor("this"));
    taskQueue.push_back(Functor("is a"));
    taskQueue.push_back(Functor("very simple"));
    taskQueue.push_back(Functor("and poorly used"));
    taskQueue.push_back(Functor("task queue"));
    for(std::list<Functor>::iterator it = taskQueue.begin();
        it != taskQueue.end(); ++it)
    {
        *it();
    }
    return 0;
}

/* prints the value stored in "i", then asks you if you want to increment it */
int i;
bool should_increment;
int doSomeWork()
{
    std::cout << "i = " << i << std::endl;
    std::cout << "increment? (enter the number 1 to increment, 0 otherwise" << std::endl;
    std::cin >> should_increment;
    return 2;
}
void doSensitiveWork()
{
     ++i;
     should_increment = false;
}
class BaseCoroutine
{
public:
    BaseCoroutine(int stat): status(stat), waiting(false){}
    void operator()(){ status = perform(); }
    int getStatus() const { return status; }
protected:
    int status;
    bool waiting;
    virtual int perform() = 0;
    bool await_status(BaseCoroutine& other, int stat, int change)
    {
        if (!waiting)
        {
            waiting = true;
        }
        if (other.getStatus() == stat)
        {
            status = change;
            waiting = false;
        }
        return !waiting;
    }
}

class MyCoroutine1: public BaseCoroutine
{
public:
    MyCoroutine1(BaseCoroutine& other): BaseCoroutine(1), partner(other){}
protected:
    BaseCoroutine& partner;
    virtual int perform()
    {
        if (getStatus() == 1)
            return doSomeWork();
        if (getStatus() == 2)
        {
            if (await_status(partner, 1))
                return 1;
            else if (i == 100)
                return 0;
            else
                return 2;
        }
    }
};

class MyCoroutine2: public BaseCoroutine
{
public:
    MyCoroutine2(bool& work_signal): BaseCoroutine(1), ready(work_signal) {}
protected:
    bool& work_signal;
    virtual int perform()
    {
        if (i == 100)
            return 0;
        if (work_signal)
        {
            doSensitiveWork();
            return 2;
        }
        return 1;
    }
};

int main()
{
     std::list<BaseCoroutine* > coroutineList;
     MyCoroutine2 *incrementer = new MyCoroutine2(should_increment);
     MyCoroutine1 *printer = new MyCoroutine1(incrementer);

     while(coroutineList.size())
     {
         for(std::list<BaseCoroutine *>::iterator it = coroutineList.begin();
             it != coroutineList.end(); ++it)
         {
             *it();
             if (*it.getStatus() == 0)
             {
                 coroutineList.erase(it);
             }
         }
     }
     delete printer;
     delete incrementer;
     return 0;
}

Конечно, сами по себе эти примеры не так полезны. Они только показывают, насколько полезны функторы, сами функторы очень простые и негибкие, и это делает их менее полезными, чем, например, то, что дает boost.

Функтор также можно использовать для имитации определения локальной функции внутри функции. См. вопрос и Другой.

Но локальный функтор не может получить доступ к внешним автоматическим переменным. Лямбда-функция (C++ 11) - лучшее решение.

Большим преимуществом реализации функций как функторов является то, что они могут поддерживать и повторно использовать состояние между вызовами. Например, многие алгоритмы динамического программирования, такие как Алгоритм Вагнера-Фишера для вычисления Расстояние Левенштейна между строками, работают, заполняя большую таблицу результатов. Очень неэффективно выделять эту таблицу каждый раз при вызове функции, поэтому реализация функции как функтора и превращение таблицы в переменную-член может значительно повысить производительность.

Ниже приведен пример реализации алгоритма Вагнера-Фишера в качестве функтора. Обратите внимание, как таблица выделяется в конструкторе, а затем повторно используется в operator() с изменением размера по мере необходимости.

#include <string>
#include <vector>
#include <algorithm>

template <typename T>
T min3(const T& a, const T& b, const T& c)
{
   return std::min(std::min(a, b), c);
}

class levenshtein_distance 
{
    mutable std::vector<std::vector<unsigned int> > matrix_;

public:
    explicit levenshtein_distance(size_t initial_size = 8)
        : matrix_(initial_size, std::vector<unsigned int>(initial_size))
    {
    }

    unsigned int operator()(const std::string& s, const std::string& t) const
    {
        const size_t m = s.size();
        const size_t n = t.size();
        // The distance between a string and the empty string is the string's length
        if (m == 0) {
            return n;
        }
        if (n == 0) {
            return m;
        }
        // Size the matrix as necessary
        if (matrix_.size() < m + 1) {
            matrix_.resize(m + 1, matrix_[0]);
        }
        if (matrix_[0].size() < n + 1) {
            for (auto& mat : matrix_) {
                mat.resize(n + 1);
            }
        }
        // The top row and left column are prefixes that can be reached by
        // insertions and deletions alone
        unsigned int i, j;
        for (i = 1;  i <= m; ++i) {
            matrix_[i][0] = i;
        }
        for (j = 1; j <= n; ++j) {
            matrix_[0][j] = j;
        }
        // Fill in the rest of the matrix
        for (j = 1; j <= n; ++j) {
            for (i = 1; i <= m; ++i) {
                unsigned int substitution_cost = s[i - 1] == t[j - 1] ? 0 : 1;
                matrix_[i][j] =
                    min3(matrix_[i - 1][j] + 1,                 // Deletion
                    matrix_[i][j - 1] + 1,                      // Insertion
                    matrix_[i - 1][j - 1] + substitution_cost); // Substitution
            }
        }
        return matrix_[m][n];
    }
};

Другие вопросы по теме