std :: function i std :: bind: czym one są i kiedy powinny być używane?

130

Wiem, czym są funktory i kiedy używać ich z stdalgorytmami, ale nie rozumiem, co mówi o nich Stroustrup w C ++ 11 FAQ .

Czy ktoś może wyjaśnić, co std::bindi std::functionkiedy należy ich używać, i podać przykłady dla początkujących?

Panie Anubis
źródło

Odpowiedzi:

202

std::bindsłuży do częściowego zastosowania funkcji .

To znaczy, załóżmy, że masz obiekt funkcji, fktóry przyjmuje 3 argumenty:

f(a,b,c);

Potrzebujesz nowego obiektu funkcji, który przyjmuje tylko dwa argumenty, zdefiniowane jako:

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

gjest "częściowym zastosowaniem" funkcji f: środkowy argument został już określony i zostały jeszcze dwa.

Możesz użyć, std::bindaby uzyskać g:

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

Jest to bardziej zwięzłe niż pisanie klasy funktora, aby to zrobić.

Dalsze przykłady znajdują się w artykule, do którego prowadzi łącze. Zwykle używa się go, gdy trzeba przekazać funktor do jakiegoś algorytmu. Masz funkcję lub funktor, który wykonuje prawie to, co chcesz, ale jest bardziej konfigurowalny (tj. Ma więcej parametrów) niż wykorzystuje algorytm. Więc wiążesz argumenty z niektórymi parametrami, a resztę pozostawiasz algorytmowi do wypełnienia:

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

Tutaj powprzyjmuje dwa parametry i może podbić do dowolnej potęgi, ale wszystko, na czym nam zależy to podbicie do potęgi 7.

Jako okazjonalne użycie, które nie jest częściową aplikacją funkcji, bindmoże również zmienić kolejność argumentów na funkcję:

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

Nie polecam go używać tylko dlatego, że nie podoba ci się API, ale ma on potencjalne praktyczne zastosowania, na przykład, ponieważ:

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

jest funkcją mniejszą lub równą (zakładając całkowity porządek, bla bla). Ten przykład zwykle nie jest konieczny, ponieważ już istnieje std::less_equal(używa <=raczej operatora niż <, więc jeśli nie są one spójne, możesz tego potrzebować, a także możesz potrzebować odwiedzić autora klasy za pomocą wskazówki). Jest to jednak rodzaj transformacji, która pojawia się, jeśli używasz funkcjonalnego stylu programowania.

Steve Jessop
źródło
18
myThread=boost::thread(boost::bind(&MyClass::threadMain, this))
Przydatne
15
Ładne wyjaśnienie wiązania. Ale co z tym std::function?
RedX
10
Twój powprzykład się nie kompiluje. Ponieważ powjest to funkcja przeciążona, musisz ręcznie określić, które przeciążenie. Wiązanie nie może pozwolić, aby zostało to wywnioskowane przez wywołującego wynikowy funktor. Np.std::transform(vec.begin(), vec.end(), out.begin(), std::bind((double (*)(double, int))std::pow, _1, 7));
MM
2
Bardzo dobrze wyjaśnione, ale czasami std::bindpojawia się wraz z thisużyciem jako drugim argumentem. Czy możesz opisać ten przypadek użycia?
Mendes
2
Również przez „_1” masz na myśli std::placeholders::_1. Zajęło mi trochę czasu, aby dowiedzieć się, dlaczego to się nie kompiluje.
terryg
26

Jednym z głównych zastosowań std :: function i std :: bind są bardziej uogólnione wskaźniki funkcji. Możesz go użyć do zaimplementowania mechanizmu wywołania zwrotnego. Jednym z popularnych scenariuszy jest to, że masz jakąś funkcję, której wykonanie zajmie dużo czasu, ale nie chcesz czekać na jej powrót, możesz uruchomić tę funkcję w oddzielnym wątku i nadać jej wskaźnik funkcji, który będzie wywołanie zwrotne po jego zakończeniu.

Oto przykładowy kod, jak tego używać:

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);
    }
};
Shital Shah
źródło
5
To świetna odpowiedź. Rozejrzałem się, aby znaleźć tę odpowiedź. Dzięki @ShitalShah
terryg
Czy mógłbyś dodać wyjaśnienie, dlaczego wiązanie pomaga uczynić je bezpieczniejszym?
Steven Lu
Mój błąd ... Nie zamierzałem mówić, że jest bardziej „bezpieczniejszy”. Zwykłe wskaźniki funkcyjne są również typesafe jednak std :: funkcja jest bardziej ogólny do pracy z lambdas, wychwytywania kontekstowego, metod itp członkowskich
Shital Shah
bind (& MyClass :: afterCompleteCallback, this, std :: placeholder :: _ 1), 2 argumenty dla 1 w definicji, void afterCompleteCallback (wynik typu float), czy może to wyjaśnić?
nonock
1
@nonock W przypadku wskaźników funkcji składowych musimy przekazać wskaźnik „this” jako pierwszy argument.
sanoj subran
12

std :: bind został wybrany do biblioteki po propozycji włączenia boost bind, przede wszystkim jest to częściowa specjalizacja funkcji, w której możesz naprawić kilka parametrów i zmienić inne w locie. Teraz jest to biblioteczny sposób robienia lambd w C ++. Jak odpowiedział Steve Jessop

Teraz, gdy C ++ 11 obsługuje funkcje lambda, nie czuję już pokusy, by używać std :: bind. Wolałbym używać curry (częściowa specjalizacja) z funkcją językową niż biblioteką.

Obiekty std :: function są funkcjami polimorficznymi. Podstawową ideą jest możliwość wymiennego odwoływania się do wszystkich wywoływalnych obiektów.

Aby uzyskać więcej informacji, chciałbym wskazać te dwa linki:

Funkcje lambda w C ++ 11: http://www.nullptr.me/2011/10/12/c11-lambda-having-fun-with-brackets/#.UJmXu8XA9Z8

Jednostka wywoływalna w C ++: http://www.nullptr.me/2011/05/31/callable-entity/#.UJmXuMXA9Z8

Sarang
źródło
5
std::bindnigdy nie istniało bez lambd - obie te funkcje zostały wprowadzone w C ++ 11. Mieliśmy, bind1sta bind2ndktóre były wychudzonymi wersjami wiązania C ++ 11.
MM
5

Użyłem go dawno temu do stworzenia puli wątków wtyczek w C ++; Ponieważ funkcja przyjmowała trzy parametry, możesz pisać w ten sposób

Załóżmy, że Twoja metoda ma podpis:

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

Aby utworzyć obiekt funkcji do powiązania trzech parametrów, możesz zrobić w ten sposób

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

Teraz, aby powiązać parametry, musimy napisać funkcję binder. A więc oto jest:

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

I funkcja pomocnicza do używania klasy 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);
}

a tutaj nas, jak to nazwać

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

Uwaga: f3 (); wywoła metodę task1-> ThreeParameterTask (21,22,23);

Aby uzyskać więcej szczegółów -> http://www.codeproject.com/Articles/26078/AC-Plug-in-ThreadPool-Design

Alex Punnen
źródło