C++
std :: function: для обертывания любого элемента, вызываемого
Поиск…
Простое использование
#include <iostream>
#include <functional>
std::function<void(int , const std::string&)> myFuncObj;
void theFunc(int i, const std::string& s)
{
std::cout << s << ": " << i << std::endl;
}
int main(int argc, char *argv[])
{
myFuncObj = theFunc;
myFuncObj(10, "hello world");
}
std :: function, используемая с std :: bind
Подумайте о ситуации, когда нам нужно вызвать функцию с аргументами. std::function
используемая с std::bind
дает очень мощную конструкцию проекта, как показано ниже.
class A
{
public:
std::function<void(int, const std::string&)> m_CbFunc = nullptr;
void foo()
{
if (m_CbFunc)
{
m_CbFunc(100, "event fired");
}
}
};
class B
{
public:
B()
{
auto aFunc = std::bind(&B::eventHandler, this, std::placeholders::_1, std::placeholders::_2);
anObjA.m_CbFunc = aFunc;
}
void eventHandler(int i, const std::string& s)
{
std::cout << s << ": " << i << std::endl;
}
void DoSomethingOnA()
{
anObjA.foo();
}
A anObjA;
};
int main(int argc, char *argv[])
{
B anObjB;
anObjB.DoSomethingOnA();
}
std :: function с lambda и std :: bind
#include <iostream>
#include <functional>
using std::placeholders::_1; // to be used in std::bind example
int stdf_foobar (int x, std::function<int(int)> moo)
{
return x + moo(x); // std::function moo called
}
int foo (int x) { return 2+x; }
int foo_2 (int x, int y) { return 9*x + y; }
int main()
{
int a = 2;
/* Function pointers */
std::cout << stdf_foobar(a, &foo) << std::endl; // 6 ( 2 + (2+2) )
// can also be: stdf_foobar(2, foo)
/* Lambda expressions */
/* An unnamed closure from a lambda expression can be
* stored in a std::function object:
*/
int capture_value = 3;
std::cout << stdf_foobar(a,
[capture_value](int param) -> int { return 7 + capture_value * param; })
<< std::endl;
// result: 15 == value + (7 * capture_value * value) == 2 + (7 + 3 * 2)
/* std::bind expressions */
/* The result of a std::bind expression can be passed.
* For example by binding parameters to a function pointer call:
*/
int b = stdf_foobar(a, std::bind(foo_2, _1, 3));
std::cout << b << std::endl;
// b == 23 == 2 + ( 9*2 + 3 )
int c = stdf_foobar(a, std::bind(foo_2, 5, _1));
std::cout << c << std::endl;
// c == 49 == 2 + ( 9*5 + 2 )
return 0;
}
«служебные» функции
std::function
может вызвать значительные накладные расходы. Поскольку std::function
имеет [значение семантики] [1], она должна скопировать или переместить данный вызываемый в себя. Но поскольку он может принимать вызовы произвольного типа, ему часто приходится выделять память динамически для этого.
Некоторые реализации function
имеют так называемую «оптимизацию небольших объектов», где небольшие объекты (например, указатели на функции, указатели элементов или функторы с очень маленьким состоянием) будут храниться непосредственно в объекте function
. Но даже это работает только в том случае, если тип noexcept
. Кроме того, стандарт C ++ не требует, чтобы все реализации обеспечивали один.
Рассмотрим следующее:
//Header file
using MyPredicate = std::function<bool(const MyValue &, const MyValue &)>;
void SortMyContainer(MyContainer &C, const MyPredicate &pred);
//Source file
void SortMyContainer(MyContainer &C, const MyPredicate &pred)
{
std::sort(C.begin(), C.end(), pred);
}
Параметр шаблона будет предпочтительным решением для SortMyContainer
, но предположим, что это невозможно или желательно по любой причине. SortMyContainer
не нужно хранить pred
за пределами своего собственного вызова. И все же, pred
может хорошо распределять память, если предоставленный ему функтор имеет некоторый нетривиальный размер.
function
выделяет память, потому что ей нужно что-то скопировать / переместить; function
переходит в собственность вызываемого. Но SortMyContainer
не должен владеть вызываемым; это просто ссылка на него. Поэтому использование function
здесь является излишним; он может быть эффективным, но может и не быть.
Нет стандартного типа библиотеки, который просто ссылается на вызываемый. Поэтому потребуется найти альтернативное решение, или вы можете жить с накладными расходами.
Кроме того, function
не имеет эффективных средств для управления тем, откуда происходят выделения памяти для объекта. Да, у него есть конструкторы, которые берут allocator
, но [многие реализации не реализуют их правильно ... или даже вообще ] [2].
Конструкторы function
которые принимают allocator
больше не являются частью типа. Поэтому нет способа управлять распределением.
Вызов function
также медленнее, чем вызов содержимого напрямую. Поскольку любой экземпляр function
может содержать любые вызываемые вызовы, вызов через function
должен быть косвенным. Накладные расходы function
вызова находятся в порядке вызова виртуальной функции.
Связывание std :: function с различными вызываемыми типами
/*
* This example show some ways of using std::function to call
* a) C-like function
* b) class-member function
* c) operator()
* d) lambda function
*
* Function call can be made:
* a) with right arguments
* b) argumens with different order, types and count
*/
#include <iostream>
#include <functional>
#include <iostream>
#include <vector>
using std::cout;
using std::endl;
using namespace std::placeholders;
// simple function to be called
double foo_fn(int x, float y, double z)
{
double res = x + y + z;
std::cout << "foo_fn called with arguments: "
<< x << ", " << y << ", " << z
<< " result is : " << res
<< std::endl;
return res;
}
// structure with member function to call
struct foo_struct
{
// member function to call
double foo_fn(int x, float y, double z)
{
double res = x + y + z;
std::cout << "foo_struct::foo_fn called with arguments: "
<< x << ", " << y << ", " << z
<< " result is : " << res
<< std::endl;
return res;
}
// this member function has different signature - but it can be used too
// please not that argument order is changed too
double foo_fn_4(int x, double z, float y, long xx)
{
double res = x + y + z + xx;
std::cout << "foo_struct::foo_fn_4 called with arguments: "
<< x << ", " << z << ", " << y << ", " << xx
<< " result is : " << res
<< std::endl;
return res;
}
// overloaded operator() makes whole object to be callable
double operator()(int x, float y, double z)
{
double res = x + y + z;
std::cout << "foo_struct::operator() called with arguments: "
<< x << ", " << y << ", " << z
<< " result is : " << res
<< std::endl;
return res;
}
};
int main(void)
{
// typedefs
using function_type = std::function<double(int, float, double)>;
// foo_struct instance
foo_struct fs;
// here we will store all binded functions
std::vector<function_type> bindings;
// var #1 - you can use simple function
function_type var1 = foo_fn;
bindings.push_back(var1);
// var #2 - you can use member function
function_type var2 = std::bind(&foo_struct::foo_fn, fs, _1, _2, _3);
bindings.push_back(var2);
// var #3 - you can use member function with different signature
// foo_fn_4 has different count of arguments and types
function_type var3 = std::bind(&foo_struct::foo_fn_4, fs, _1, _3, _2, 0l);
bindings.push_back(var3);
// var #4 - you can use object with overloaded operator()
function_type var4 = fs;
bindings.push_back(var4);
// var #5 - you can use lambda function
function_type var5 = [](int x, float y, double z)
{
double res = x + y + z;
std::cout << "lambda called with arguments: "
<< x << ", " << y << ", " << z
<< " result is : " << res
<< std::endl;
return res;
};
bindings.push_back(var5);
std::cout << "Test stored functions with arguments: x = 1, y = 2, z = 3"
<< std::endl;
for (auto f : bindings)
f(1, 2, 3);
}
Выход:
Test stored functions with arguments: x = 1, y = 2, z = 3
foo_fn called with arguments: 1, 2, 3 result is : 6
foo_struct::foo_fn called with arguments: 1, 2, 3 result is : 6
foo_struct::foo_fn_4 called with arguments: 1, 3, 2, 0 result is : 6
foo_struct::operator() called with arguments: 1, 2, 3 result is : 6
lambda called with arguments: 1, 2, 3 result is : 6
Сохранение аргументов функции в std :: tuple
Некоторым программам необходимо хранить аргументы для будущего вызова некоторой функции.
В этом примере показано, как вызвать любую функцию с аргументами, хранящимися в std :: tuple
#include <iostream>
#include <functional>
#include <tuple>
#include <iostream>
// simple function to be called
double foo_fn(int x, float y, double z)
{
double res = x + y + z;
std::cout << "foo_fn called. x = " << x << " y = " << y << " z = " << z
<< " res=" << res;
return res;
}
// helpers for tuple unrolling
template<int ...> struct seq {};
template<int N, int ...S> struct gens : gens<N-1, N-1, S...> {};
template<int ...S> struct gens<0, S...>{ typedef seq<S...> type; };
// invocation helper
template<typename FN, typename P, int ...S>
double call_fn_internal(const FN& fn, const P& params, const seq<S...>)
{
return fn(std::get<S>(params) ...);
}
// call function with arguments stored in std::tuple
template<typename Ret, typename ...Args>
Ret call_fn(const std::function<Ret(Args...)>& fn,
const std::tuple<Args...>& params)
{
return call_fn_internal(fn, params, typename gens<sizeof...(Args)>::type());
}
int main(void)
{
// arguments
std::tuple<int, float, double> t = std::make_tuple(1, 5, 10);
// function to call
std::function<double(int, float, double)> fn = foo_fn;
// invoke a function with stored arguments
call_fn(fn, t);
}
Выход:
foo_fn called. x = 1 y = 5 z = 10 res=16