С++ Функторы - и их использование

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

Ответ 1

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

// this is a functor
struct add_x {
  add_x(int x) : x(x) {}
  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 не является жестко запрограммированным, оно было указано как аргумент конструктора, когда мы создали наш экземпляр functor. Я мог бы создать еще один сумматор, добавив 27, просто вызвав конструктор с другим значением. Это делает их красиво настраиваемыми.

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

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

Ответ 2

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

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

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

Ответ 3

Функтор - это объект, который действует как функция. В принципе, класс, который определяет 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)
{ ....}

Ответ 4

Название "functor" традиционно используется в теория категорий задолго до появления на сцене С++. Это не имеет ничего общего с концепцией функтора С++. Лучше использовать имя объект функции вместо того, что мы называем "функтором" на С++. Вот как другие языки программирования называют подобные конструкции.

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

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

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

Минусы:

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

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

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

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

Минусы:

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

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

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

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

Минусы:

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

Ответ 5

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

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

Ответ 6

Для новичков, подобных мне среди нас: после небольшого исследования я выяснил, что сделал код 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.
}; 

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

Ответ 7

Функтор - это функция map более высокого порядка. Например, мы могли бы определить функтор для 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));

Ответ 8

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

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

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

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

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

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


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

Ответ 9

Кроме использования в обратном вызове, С++-функторы также могут помочь обеспечить стиль доступа Matlab к классу матрицы. Существует пример.

Ответ 10

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


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

void* method(void* something)

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

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

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

Ответ 11

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

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

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

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

Ответ 12

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

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

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

/* 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.

Ответ 13

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

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

Ответ 14

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

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

Ответ 15

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