std::function и std::bind: какви са те и кога трябва да се използват?

Знам какво представляват функторите и кога да ги използвам с std алгоритми, но не разбрах какво казва Строуструп за тях в ЧЗВ за C++11.

Може ли някой да обясни какво представляват std::bind и std::function, кога трябва да се използват и да даде някои примери за начинаещи?


person Mr.Anubis    schedule 07.07.2011    source източник


Отговори (4)


std::bind е за приложение с частична функция.

Това означава, че имате функционален обект f, който приема 3 аргумента:

f(a,b,c);

Искате нов функционален обект, който приема само два аргумента, дефинирани като:

g(a,b) := f(a, 4, b);

g е "частично приложение" на функцията f: средният аргумент вече е зададен и остават още два.

Можете да използвате std::bind, за да получите g:

auto g = bind(f, _1, 4, _2);

Това е по-сбито от действителното писане на клас функтор, за да го направи.

Има още примери в статията, към която препращате. Обикновено го използвате, когато трябва да предадете функтор на някакъв алгоритъм. Имате функция или функтор, който почти върши работата, която искате, но е по-конфигурируем (т.е. има повече параметри), отколкото използва алгоритъмът. Така че свързвате аргументи с някои от параметрите и оставяте останалите за алгоритъма да ги попълни:

// raise every value in vec to the power of 7
std::transform(vec.begin(), vec.end(), some_output, std::bind(std::pow, _1, 7));

Тук pow приема два параметъра и може да повиши на всяка степен, но всичко, което ни интересува, е да повишим на степен 7.

Като случайна употреба, която не е частично приложение на функция, bind може също да пренареди аргументите на функция:

auto memcpy_with_the_parameters_in_the_right_flipping_order = bind(memcpy, _2, _1, _3);

Не препоръчвам да го използвате само защото не харесвате API, но има потенциални практически приложения, например защото:

not2(bind(less<T>, _2, _1));

е функция по-малко от или равно (приемайки общ ред, бла-бла). Този пример обикновено не е необходим, тъй като вече има std::less_equal (използва оператора <=, а не <, така че ако те не са последователни, може да имате нужда от това и може също да се наложи да посетите автора на класа с клечка). Все пак това е вид трансформация, която се появява, ако използвате функционален стил на програмиране.

person Steve Jessop    schedule 07.07.2011
comment
Също така удобен за обратни извиквания към членски функции: myThread=boost::thread(boost::bind(&MyClass::threadMain, this)) - person rlduffy; 09.07.2011
comment
Хубаво обяснение на bind. Но какво да кажем за std::function? - person RedX; 03.04.2012
comment
Вашият pow пример не се компилира. Тъй като pow е претоварена функция, трябва ръчно да посочите кое претоварване. Свързването не може да го остави да бъде изведено от извикващия произтичащия функтор. напр. std::transform(vec.begin(), vec.end(), out.begin(), std::bind((double (*)(double, int))std::pow, _1, 7)); - person M.M; 30.12.2015
comment
Много добре обяснено, но понякога std::bind се използва заедно с this като втори аргумент. Можете ли да разработите този случай на употреба? - person Mendes; 01.03.2016
comment
@Mendes: Ако функцията, която обвързвате, е членска функция на който и да е клас, вие използвате това като втори параметър, а третите параметри са заместители. - person Roshan Mehta; 26.03.2018
comment
Освен това под _1 имате предвид std::placeholders::_1. Отне ми известно време, за да разбера защо това не се компилира. - person terryg; 20.12.2018

Една от основните употреби на std::function и std::bind е като по-генерализирани функционални указатели. Можете да го използвате за прилагане на механизъм за обратно извикване. Един от популярните сценарии е, че имате някаква функция, която ще отнеме много време за изпълнение, но не искате да чакате да се върне, след което можете да изпълните тази функция в отделна нишка и да й дадете указател на функция, който ще обратно извикване, след като приключи.

Ето примерен код за това как да използвате това:

class MyClass {
private:
    //just shorthand to avoid long typing
    typedef std::function<void (float result)> TCallback;

    //this function takes long time
    void longRunningFunction(TCallback callback)
    {
        //do some long running task
        //...
        //callback to return result
        callback(result);
    }

    //this function gets called by longRunningFunction after its done
    void afterCompleteCallback(float result)
    {
        std::cout << result;
    }

public:
    int longRunningFunctionAsync()
    {
        //create callback - this equivalent of safe function pointer
        auto callback = std::bind(&MyClass::afterCompleteCallback, 
            this, std::placeholders::_1);

        //normally you want to start below function on seprate thread, 
        //but for illustration we will just do simple call
        longRunningFunction(callback);
    }
};
person Shital Shah    schedule 03.12.2016
comment
Това е страхотен отговор. Прегледах навсякъде, за да намеря този отговор. Благодаря @ShitalShah - person terryg; 20.12.2018
comment
Бихте ли добавили обяснение защо обвързването помага да стане по-безопасно? - person Steven Lu; 20.09.2019
comment
Лошото ми... Не исках да кажа, че е по-безопасно. Нормалните функционални указатели също са безопасни за типове, но std::function е по-генерична за работа с ламбда, улавяне на контекст, методи на член и т.н. - person Shital Shah; 23.09.2019
comment
bind(&MyClass::afterCompleteCallback, this, std::placeholders::_1) , 2 аргумента за 1 в дефиницията, void afterCompleteCallback(float result) , може ли да обясни това? - person ; 05.01.2020
comment
@nonock За функционални указатели на членски функции трябва да предадем този указател като първи аргумент. - person sanoj subran; 18.07.2020

std::bind беше гласуван в библиотека след предложение за включване на boost bind, основно това е частична специализация на функцията, където можете да коригирате няколко параметъра и да промените други в движение. Сега това е библиотечен начин за правене на ламбда в C++. Както отговори Стив Джесъп

Сега, когато C++11 поддържа ламбда функции, вече не изпитвам никакво изкушение да използвам std::bind. Предпочитам да използвам къри (частична специализация) с езикова функция, отколкото библиотечна функция.

std::function обектите са полиморфни функции. Основната идея е да можете да препращате към всички извикваеми обекти взаимозаменяемо.

Бих ви насочил към тези две връзки за повече подробности:

Ламбда функции в C++11: http://www.nullptr.me/2011/10/12/c11-lambda-having-fun-with-brackets/#.UJmXu8XA9Z8

Извикваем обект в C++: http://www.nullptr.me/2011/05/31/callable-entity/#.UJmXuMXA9Z8

person Sarang    schedule 06.11.2012
comment
std::bind никога не е съществувал без ламбда - и двете функции са въведени в C++11. Имахме bind1st и bind2nd, които бяха изтощени версии на C++11 bind. - person M.M; 30.12.2015
comment
Домейнът nullptr.me вече не съществува. - person Jabberwocky; 16.04.2021

Използвах го много време назад, за да създам пул от нишки на плъгини в C++; Тъй като функцията приемаше три параметъра, можете да напишете така

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

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

За да създадете функционален обект за обвързване на трите параметъра, можете да направите така

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

Сега, за да обвържем параметрите, трябва да напишем свързваща функция. И така, ето го:

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

и ето как да го наречем

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

Забележка: f3(); ще извика метода task1->ThreeParameterTask(21,22,23);

За повече кървави подробности --> http://www.codeproject.com/Articles/26078/A-C-Plug-in-ThreadPool-Design

person Alex Punnen    schedule 14.02.2014