std :: function та std :: bind: що вони таке, і коли їх слід використовувати?


127

Я знаю, що таке функтори та коли їх використовувати з stdалгоритмами, але я не зрозумів, що Stroustrup каже про них у поширених запитаннях C ++ 11 .

Чи може хтось пояснити, що std::bindі що std::function, коли їх слід використовувати, та навести кілька прикладів для новачків?

Відповіді:


200

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(він використовує <=оператор, а не <, тому, якщо вони не відповідають, вам може знадобитися це, і вам може знадобитися відвідати автора класу із підказкою). Це така трансформація, яка виникає, якщо ви використовуєте функціональний стиль програмування.


18
Також зручно для зворотних викликів функцій учасників:myThread=boost::thread(boost::bind(&MyClass::threadMain, this))
rlduffy

15
Приємне пояснення зв’язування. А як же std::function?
RedX

10
Ваш powприклад не складається. Оскільки powце перевантажена функція, вам потрібно вручну вказати, яке перевантаження. Пов’язання не може залишити його виводити абонентом результуючого функтора. Напр.std::transform(vec.begin(), vec.end(), out.begin(), std::bind((double (*)(double, int))std::pow, _1, 7));
ММ

2
Дуже добре пояснено, але іноді std::bindпоєднується з thisвикористанням як другим аргументом. Чи можете ви, будь ласка, розробити цей випадок використання?
Мендес

2
Також ви маєте на увазі "_1" std::placeholders::_1. Зайняв мене деякий час, щоб з’ясувати, чому це не складено.
terryg

25

Одним з головних напрямків використання 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);
    }
};

5
Це чудова відповідь. Я роздивився всюди, щоб знайти цю відповідь. Спасибі @ShitalShah
terryg

Чи можете ви додати пояснення, чому прив'язка допомагає зробити це безпечнішим?
Стівен Лу

Моє погано ... Я не збирався говорити, що це "безпечніше". Покажчики на нормальну функцію також є typesafe, але std :: функція є більш загальною для роботи з лямбдами, захопленням контексту, методами членів тощо
Shital Shah

bind (& MyClass :: afterCompleteCallback, this, std :: placewers :: _ 1), 2 аргументи для 1 у визначенні, void afterCompleteCallback (float результат), може це пояснити?
нонок

1
@nonock Для покажчиків функцій функцій-членів нам потрібно передати «цей» покажчик як перший аргумент.
sanoj subran

12

std :: bind проголосували в бібліотеці після пропозиції включити прискорене прив'язування, в першу чергу це спеціалізація з частковою функцією, за допомогою якої ви можете зафіксувати декілька параметрів та змінити інші під час руху. Тепер це бібліотечний спосіб робити лямбда на C ++. На що відповів Стів Джессоп

Тепер, коли C ++ 11 підтримує лямбда-функції, я більше не відчуваю спокуси використовувати std :: bind. Я вважаю за краще використовувати currying (часткова спеціалізація) з мовною особливістю, ніж функцією бібліотеки.

std :: функції об'єктів - це поліморфні функції. Основна ідея - вміти посилатися на всі об'єкти, що дзвоняться, взаємозамінно.

Я хотів би вказати на ці два посилання для отримання детальної інформації:

Функції лямбда в 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


5
std::bindніколи не існувало без лямбдів - обидві ці особливості були введені в C ++ 11. У нас були bind1stі bind2ndякі були зняті версії C ++ 11 прив'язки.
ММ

5

Я довго використовував це для створення пулу потоків плагінів у 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/AC-Plug-in-ThreadPool-Design

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.