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

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

10.12.2008 17:47:21
Эта тема была рассмотрена в ответ на этот вопрос: stackoverflow.com/questions/317450/why-override-operator#317528
Luc Touraille 10.12.2008 18:00:56
Он используется для создания замыкания в C ++.
copper.hat 30.06.2019 04:13:43
15 ОТВЕТОВ
РЕШЕНИЕ

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

// 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(). Это означает, что он может встроить этот вызов функции. И это делает его таким же эффективным, как если бы я вручную вызывал функцию для каждого значения вектора.

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

1036
10.01.2020 20:55:17
Можете ли вы объяснить эту строку, пожалуйста, std :: transform (in.begin (), in.end (), out.begin (), add_x (1)); почему вы пишете там add_x, а не add42?
Alecs 12.09.2011 14:48:48
@Alecs Оба сработали бы (но эффект был бы другим). Если бы я использовал add42, я бы использовал созданный ранее функтор и добавил 42 к каждому значению. С помощью add_x(1)я создаю новый экземпляр функтора, который добавляет только 1 к каждому значению. Просто для того, чтобы показать, что часто вы создаете экземпляр функтора «на лету», когда вам это нужно, вместо того, чтобы сначала его создавать, и сохраняете его перед тем, как фактически использовать его для чего-либо.
jalf 12.09.2011 15:12:59
@zadane конечно. Они просто должны иметь operator(), потому что это то, что звонящий использует для вызова. Что еще есть у функтора функций-членов, конструкторов, операторов и переменных-членов, полностью зависит от вас.
jalf 9.02.2014 12:37:44
@ rikimaru2013 С точки зрения функционального программирования, вы правы, функция также является функтором, но на языке C ++ функтор определенно является классом, используемым в качестве функции. Терминология была немного злоупотреблена на ранних этапах, но это разделение является полезным различием и поэтому сохраняется и сегодня. Если вы начнете ссылаться на функции как «функторы» в контексте C ++, тогда вы просто запутаете разговор.
srm 28.03.2016 22:09:46
Это класс или экземпляр класса? В большинстве источников add42будет называться функтором, а не add_x(это класс функтора или просто класс функтора). Я считаю эту терминологию непротиворечивой, потому что функторы также называются объектами функций , а не классами функций. Можете ли вы уточнить этот момент?
Sergei Tachenov 13.06.2016 14:41:05

Функтор - это объект, который действует как функция. В основном, класс, который определяет 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)
{ ....}
101
7.06.2017 11:34:28
Просто нужно добавить, что они могут быть использованы как указатель на функцию.
Martin York 10.12.2008 18:04:47
@LokiAstari - Для тех, кто не знаком с этой концепцией, это может ввести в заблуждение. Функторы можно «использовать как», но не всегда «вместо» указателей на функции. Например, функция, которая принимает указатель функции, не может занять функтор на своем месте, даже если у функтора те же аргументы и возвращаемое значение, что и у указателя функции. Но в целом при проектировании функторы являются предпочтительным и теоретически «более современным» способом.
MasonWinsauer 12.03.2014 21:49:50
Почему второй возвращается, intкогда должен вернуться bool? Это C ++, а не C. Когда этот ответ был написан, boolне существовало?
Fund Monica's Lawsuit 8.05.2016 17:40:33
@QPaysTaxes Опечатка, я думаю. Я, вероятно, скопировал и скопировал код из первого примера и забыл изменить его. Я исправил это сейчас.
James Curran 10.05.2016 18:09:41
@Riasat Если Matcher находится в библиотеке, определить Is5 () довольно просто. И вы можете создать Is7 (), Is32 () и т. Д. Далее, это только пример. Функтор может быть намного сложнее.
James Curran 30.09.2017 02:57:19

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

Функторы обычно используются в алгоритмах 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, если этот вызов не был встроен, и компилятор не знает, что вы всегда передаете ему одну и ту же функцию, он не сможет встроить вызов через указатель.

41
8.09.2015 12:03:00

Маленькое дополнение. Вы можете использовать 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));

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

121
7.06.2017 11:33:44
Не должно operator ()быть публично в вашем первом примере, так как классы по умолчанию закрыты?
NathanOliver 16.06.2015 13:43:19
возможно, в какой-то момент этот ответ заслуживает обновления, поскольку теперь лямбды - это самый простой способ получить функтор от чего угодно
idclev 463035818 30.01.2019 18:29:53

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

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

Особенности:

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

Минусы:

  • Приносит больше сложности в программу.

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

Особенности:

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

Минусы:

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

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

Особенности:

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

Минусы:

  • Функциональный объект не может быть заменен другим типом функционального объекта во время выполнения (по крайней мере, если он не расширяет некоторый базовый класс, который, следовательно, дает некоторые накладные расходы)
51
13.02.2017 10:46:57
Можете ли вы объяснить эти варианты использования в реальном примере? Как мы можем использовать функторы как полиморфизм и указатель функции?
Milad Khajavi 21.02.2013 17:40:27
Что на самом деле означает, что функтор хранит состояние?
erogol 15.04.2013 12:05:41
спасибо за указание на то, что нужен базовый класс, чтобы иметь какой-то полиморфизм. У меня просто проблема в том, что я должен использовать функтор в том же месте, что и простой указатель функции, и единственный способ, который я нашел, это написать базовый класс функтора (так как я не могу использовать C ++ 11). Не был уверен, что эти накладные расходы имеют смысл, пока я не прочитал твой ответ.
idclev 463035818 9.01.2015 12:36:42
@Erogol Функтор - это объект, который поддерживает синтаксис foo(arguments). Следовательно, он может содержать переменные; например, если у вас была update_password(string)функция, вы можете отслеживать, как часто это происходило; с функтором, который может private long timeпредставлять метку времени, когда это произошло в последний раз. С указателем на функцию или простой функцией вам нужно будет использовать переменную вне ее пространства имен, которая напрямую связана только с документацией и использованием, а не с определением.
Fund Monica's Lawsuit 9.05.2016 17:10:49
⁺¹ за упоминание того, что название было выдумано без причины. Я только что искал, какова связь между математическим (или функциональным, если хотите) функтором и C ++.
Hi-Angel 15.10.2017 18:25:48

Я «открыл» очень интересное использование функторов: я использую их, когда у меня нет подходящего имени для одного метода, поскольку функтор - это метод без имени ;-)

-10
16.02.2011 13:29:25
Почему вы описываете функтор как «метод без имени»?
Anderson Green 8.01.2013 03:05:10
Функция без имени называется лямбда-выражением.
Paul Fultz II 18.08.2013 15:21:50

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


Если вы используете библиотеку pthread, чтобы сделать ваше приложение многопоточным, Functors могут вам помочь.
Чтобы запустить поток, одним из аргументов 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()методом.

2
24.10.2011 18:57:24

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

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

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

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

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

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


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

9
26.12.2011 06:54:56
Если ваш функтор вызывал разные конкретные функции, и эти другие функции различались по количеству параметров, которые они принимают, означает ли это, что ваш функтор принял переменное число аргументов для отправки этим другим функциям?
johnbakers 28.02.2014 06:09:07
не могли бы вы объяснить вышеприведенный сценарий, цитируя некоторую часть кода, я новичок в c ++, хочу понять эту концепцию ..
sanjeev 5.09.2017 10:13:32

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

3
7.03.2012 07:03:17
Это (пример матрицы) простое использование, operator()но не использование свойств объекта функции.
renardesque 4.07.2019 09:40:00

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

Функтор - это объект класса или структуры, который можно «вызвать» как функцию. Это стало возможным благодаря перегрузке () 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.
}; 

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

37
22.09.2015 21:26:14
Оператор () называется оператором вызова функции. Я думаю, вы могли бы также назвать это оператором скобок.
Gautam 12.10.2013 20:19:11
«Этот параметр на самом деле является аргументом« parameterVar », передаваемым конструктором, которого мы только что написали« А ?
Lightness Races in Orbit 8.09.2015 12:04:04

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

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

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

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

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

// 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) ;.

Полный контекст этого шаблона по следующей ссылке

2
28.01.2013 06:50:37

Функтор - это функция высшего порядка, которая применяет функцию к параметризованным (то есть шаблонным) типам. Это обобщение функции высшего порядка отображения . Например, мы могли бы определить функтор для 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));
22
12.02.2017 17:12:35
Статья утверждая , что функтор правильно следует использовать для этого значения (см также en.wikipedia.org/wiki/Functor ), и использовать его для функциональных объектов просто неаккуратно: jackieokay.com/2017/01/26/functors.html It может быть, слишком поздно для этого, учитывая количество ответов здесь, которые рассматривают только значение объекта функции.
armb 12.02.2017 13:33:44
Этот ответ должен быть тот, с> 700 Upvotes. Как кто-то, кто знает Haskell лучше, чем C ++, лингва C ++ меня все время удивляла.
mschmidt 26.12.2017 21:34:38
Теория категорий и C ++? Это секретный счет Бартоша Милевски?
Mateen Ulhaq 7.08.2018 23:35:00
Может быть полезно обобщить законы функторов в стандартных обозначениях: fmap(id, x) = id(x)и fmap(f ◦ g, x) = fmap(f, fmap(g, x)).
Mateen Ulhaq 7.08.2018 23:40:26
@mschmidt, в то время как functor также означает это, C ++ перегружает имя, чтобы означать то же самое, что и «функциональный объект»
Caleth 2.07.2019 10:16:34

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

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

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

/* 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;
}

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

3
8.02.2014 05:34:56

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

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

1
23.05.2017 12:34:53

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

Ниже приведен пример реализации алгоритма Вагнера-Фишера в качестве функтора. Обратите внимание, как таблица размещается в конструкторе, а затем используется повторно 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];
    }
};
2
16.01.2017 09:56:58