Что такое функторы C++ и их использование?
Я много слышу о функторах в C++. Может ли кто-нибудь дать мне обзор того, что они такое и в каких случаях они были бы полезны?
15 ответов
функтор-это просто класс, который определяет оператор(). Это позволяет создавать объекты ,которые "похожи" на функцию:
// 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 не жестко закодировано, оно было указано как аргумент конструктора, когда мы создали наш экземпляр функтора. Я мог бы создать еще одну гадюку., который добавил 27, просто вызвав конструктор с другим значением. Это делает их хорошо настраиваемыми.
как показывают последние строки, вы часто передаете функторы в качестве аргументов другим функциям, таким как std::transform или другие стандартные алгоритмы библиотеки. Вы можете сделать то же самое с указателем регулярной функции, за исключением того, как я сказал выше, функторы могут быть "настроены", потому что они содержат состояние, что делает их более гибкими (если бы я хотел использовать указатель функции, мне пришлось бы написать функция, которая добавила ровно 1 к своему аргументу. Функтор является общим и добавляет все, с чем вы его инициализировали), и они также потенциально более эффективны. В приведенном выше примере, компилятор точно знает, какие функции std::transform
должны позвонить. Он должен позвонить add_x::operator()
. Это означает, что он может встроить этот вызов функции. И это делает его столь же эффективным, как если бы я вручную вызывал функцию для каждого значения вектора.
Если бы вместо этого я передал указатель функции, компилятор не удалось сразу увидеть, на какую функцию он указывает, поэтому, если он не выполняет некоторые довольно сложные глобальные оптимизации, ему придется разыменовать указатель во время выполнения, а затем сделать вызов.
маленькое дополнение. Вы можете использовать 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()
.
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)
{ ....}
имя "функтор" традиционно используется в теория категорий задолго до появления C++ на сцене. Это не имеет ничего общего с концепцией функтора C++. Лучше использовать name объект вместо того, что мы называем "функтор" в C++. Именно так другие языки программирования называют подобные конструкции.
используется вместо простой функции:
характеристики:
- объект функции может быть государство
- объект функции помещается в ООП (он ведет себя как любой другой объект).
плюсы:
- приносит больше сложности в программу.
используется вместо указателя функции:
характеристики:
- объект функции часто может быть подставлена
плюсы:
- объект функции не может быть заменен другим типом объекта функции во время выполнения (по крайней мере, если он не расширяет некоторый базовый класс, который поэтому дает некоторые накладные расходы)
используется вместо виртуальной функции:
характеристики:
- объект функции (не виртуальный) не требует диспетчеризации vtable и runtime, поэтому в большинстве случаев он более эффективен
плюсы:
- объект функции не может быть заменен другим типом объекта функции во время выполнения (по крайней мере, если он не расширяется некоторый базовый класс, который поэтому дает некоторые накладные расходы)
как уже упоминалось, функтор-это объект, который действует как функция, т. е. он перегружает оператор вызова функции.
функторы обычно используются в алгоритмах 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
, если это вызов получил встроенный, и компилятор знает, что вы всегда передаете ему одну и ту же функцию, он не может встроить вызов через указатель.
для новичков, таких как я среди нас: после небольшого исследования я понял, что сделал код jalf posted.
функтор - это класс или объект структуры, который может быть "вызван" как функция. Это стало возможным благодаря перегрузке () operator
. The () operator
(Не уверен, что его называют) может принимать любое количество аргументов. Другие операторы принимают только два, т. е. + operator
может принимать только два значения (по одному с каждой стороны оператора) и возвращать любое значение, для которого вы его перегрузили. Вы может поместиться любое количество аргументов внутри () operator
что дает ему гибкость.
чтобы создать функтор, сначала создайте свой класс. Затем вы создаете конструктор для класса с параметром по вашему выбору типа и имени. За этим в том же операторе следует список инициализаторов (который использует один оператор двоеточия, к которому я также был новичком), который создает объекты-члены класса с ранее объявленным параметром конструктору. Тогда () operator
is перегруженный. Наконец, вы объявляете частные объекты созданного вами класса или структуры.
мой код (я нашел имена переменных 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.
};
если что-либо из этого неточно или просто неправильно, не стесняйтесь поправлять меня!
функтор-это функция более высокого порядка это применяет функцию к параметризованным (т. е. шаблонным) типам. Это обобщение карта функция более высокого порядка. Например, мы могли бы определить функтор для 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)));
}
Heres простой пример, который преобразует тип в 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));
вот фактическая ситуация, когда я был вынужден использовать функтор для решения моей проблемы:
У меня есть набор функций (скажем, 20 из них), и все они идентичны, за исключением того, что каждый вызывает другую конкретную функцию в 3 определенных местах.
Это невероятные отходы и дублирование кода. Обычно я просто передаю указатель функции и просто вызываю его в 3 местах. (Таким образом, код должен появиться только один раз, а не двадцать раз.)
но тогда я реализованная, в каждом конкретном случае, конкретная функция требовала совершенно другого профиля параметров! Иногда 2 параметра, иногда 5 параметров и т. д.
другим решением было бы иметь базовый класс, где конкретная функция является переопределенным методом в производном классе. Но действительно ли я хочу построить все это наследование, чтобы я мог передать указатель функции????
решение: так что я сделал, я сделал класс-оболочку ("функтор"), который может вызвать любой из функции мне нужны называется. Я установил его заранее (с его параметрами и т. д.), а затем передаю его вместо указателя функции. Теперь вызываемый код может вызвать функтор, не зная, что происходит внутри. Он даже может вызвать его несколько раз (мне нужно было позвонить 3 раза.)
вот и все-практический пример, где функтор оказался очевидным и простым решением, которое позволило мне уменьшить дублирование кода с 20 функций до 1.
за исключением используемого в обратном вызове, функторы C++ также могут помочь предоставить Matlab нравится стиль доступа к матрица класса. Есть пример.
функторы используются в gtkmm для подключения некоторой кнопки GUI к фактической функции или методу c++.
если вы используете библиотеку pthread, чтобы сделать ваше приложение многопоточным, функторы могут вам помочь.
Чтобы запустить поток, один из аргументов pthread_create(..)
- указатель функции, который будет выполняться в его собственном потоке.
Но есть одно неудобство. Этот указатель не может быть указателем на метод, если это статический метод, или если вы укажите класс, как class::method
. И еще одно, интерфейс вашего метода может быть только:
void* method(void* something)
таким образом, вы не можете запускать (простым очевидным способом) методы из вашего класса в потоке, не делая ничего лишнего.
очень хороший способ работы с потоками на C++ - это создание собственного Thread
класса. Если вы хотите запустить методы из MyClass
класс, то, что я сделал, это преобразовать эти методы в Functor
производные классы.
и Thread
класс имеет этот метод:
static void* startThread(void* arg)
Указатель на этот метод будет использоваться в качестве аргумента для вызова pthread_create(..)
. И что?!--9--> должен получить в arg является void*
приведенная ссылка на экземпляр в куче любого Functor
производный класс, который будет возвращен в Functor*
при выполнении, а затем называется это run()
метод.
чтобы добавить, я использовал объекты функций, чтобы соответствовать существующему устаревшему методу шаблона команды; (только место, где красота парадигмы OO true OCP я чувствовал ); Также здесь добавляется связанный шаблон адаптера функции.
предположим, что ваш метод имеет подпись:
int CTask::ThreeParameterTask(int par1, int par2, int par3)
мы увидим, как мы можем подогнать его для шаблона команды - для этого, во-первых, вы должны написать адаптер функции-члена, чтобы его можно было вызвать как объект функции.
Примечание. - это уродливо, и, возможно,вы можете использовать помощники 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);.
полный контекст этого шаблона в следующем ссылке
Как было повторено, функторы-это классы, которые можно рассматривать как функции (оператор перегрузки ()).
они наиболее полезны в ситуациях, когда вам нужно связать некоторые данные с повторными или отложенными вызовами функции.
например, связанный список функторов может использоваться для реализации базовой синхронной системы с низкими накладными расходами, диспетчера задач или разбора прерываемых файлов. Примеры:
/* 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.
большим преимуществом реализации функций в качестве функторов является то, что они могут поддерживать и повторно использовать состояние между вызовами. Например, многие алгоритмы динамического программирования, такие как алгоритм Вагнера-Фишера расчета расстояние Левенштейна между строками, работа, заполнив большую таблицу результатов. Очень неэффективно выделять эту таблицу каждый раз, когда вызывается функция, поэтому реализация функции как функтора и создание таблицы членом переменная может значительно улучшить производительность.
Ниже приведен пример реализации алгоритма Вагнера-Фишера в качестве функтора. Обратите внимание, как таблица выделяется в конструкторе, а затем повторно в 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];
}
};
Я "открыл" очень интересное использование функторов: я использую их, когда у меня нет хорошего имени для одного метода, так как функтор-это метод без имени ; -)