std :: function dan std :: bind: apa itu, dan kapan harus digunakan?

129

Saya tahu apa itu functor dan kapan menggunakannya dengan stdalgoritme, tetapi saya belum mengerti apa yang dikatakan Stroustrup tentang mereka di FAQ C ++ 11 .

Adakah yang bisa menjelaskan apa std::binddan apa std::function, kapan mereka harus digunakan, dan memberikan beberapa contoh untuk pemula?

Tuan Anubis
sumber

Jawaban:

201

std::bindadalah untuk aplikasi fungsi parsial .

Artinya, misalkan Anda memiliki objek fungsi fyang membutuhkan 3 argumen:

f(a,b,c);

Anda menginginkan objek fungsi baru yang hanya membutuhkan dua argumen, yang didefinisikan sebagai:

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

gadalah "aplikasi parsial" dari fungsi tersebut f: argumen tengah telah ditentukan, dan masih ada dua yang tersisa.

Anda bisa menggunakan std::binduntuk mendapatkan g:

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

Ini lebih ringkas daripada menulis kelas functor untuk melakukannya.

Ada contoh lebih lanjut di artikel yang Anda tautkan. Anda biasanya menggunakannya saat Anda perlu meneruskan functor ke beberapa algoritme. Anda memiliki fungsi atau functor yang hampir melakukan pekerjaan yang Anda inginkan, tetapi lebih dapat dikonfigurasi (yaitu memiliki lebih banyak parameter) daripada yang digunakan algoritme. Jadi, Anda mengikat argumen ke beberapa parameter, dan membiarkan sisanya untuk diisi oleh algoritme:

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

Di sini, powmengambil dua parameter dan dapat menaikkan ke daya apa pun , tetapi yang kami pedulikan hanyalah meningkatkan ke pangkat 7.

Sebagai penggunaan sesekali yang bukan merupakan aplikasi fungsi parsial, bindjuga dapat menyusun ulang argumen ke suatu fungsi:

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

Saya tidak menyarankan menggunakannya hanya karena Anda tidak menyukai API, tetapi memiliki potensi penggunaan praktis misalnya karena:

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

adalah fungsi kurang dari atau sama (dengan asumsi urutan total, bla bla). Contoh ini biasanya tidak diperlukan karena sudah ada std::less_equal(ini menggunakan <=operator daripada <, jadi jika mereka tidak konsisten maka Anda mungkin memerlukan ini, dan Anda mungkin juga perlu mengunjungi penulis kelas dengan klik). Ini adalah jenis transformasi yang muncul jika Anda menggunakan gaya pemrograman fungsional.

Steve Jessop
sumber
18
Juga berguna untuk panggilan balik ke fungsi anggota:myThread=boost::thread(boost::bind(&MyClass::threadMain, this))
rlduffy
15
Penjelasan bagus tentang bind. Tapi bagaimana dengan std::function?
RedX
10
powContoh Anda tidak dapat dikompilasi. Karena powmerupakan fungsi yang kelebihan beban, Anda harus menentukan kelebihan beban secara manual. Pengikatan tidak dapat meninggalkannya untuk disimpulkan oleh pemanggil dari functor yang dihasilkan. Misalnyastd::transform(vec.begin(), vec.end(), out.begin(), std::bind((double (*)(double, int))std::pow, _1, 7));
MM
2
Dijelaskan dengan sangat baik, tetapi terkadang std::bindmuncul bersamaan dengan thispenggunaan sebagai argumen ke-2. Bisakah Anda menjelaskan kasus penggunaan ini?
Mendes
2
Juga dengan "_1" yang Anda maksud std::placeholders::_1. Butuh beberapa saat untuk mencari tahu mengapa ini tidak bisa dikompilasi.
terryg
26

Salah satu penggunaan utama std :: function dan std :: bind adalah sebagai pointer fungsi yang lebih umum. Anda dapat menggunakannya untuk mengimplementasikan mekanisme callback. Salah satu skenario populer adalah Anda memiliki beberapa fungsi yang akan membutuhkan waktu lama untuk dieksekusi tetapi Anda tidak ingin menunggu untuk kembali, maka Anda dapat menjalankan fungsi itu pada utas terpisah dan memberikannya penunjuk fungsi yang akan dijalankan. callback setelah selesai.

Berikut adalah contoh kode untuk menggunakan ini:

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
sumber
5
Ini jawaban yang bagus. Saya telah mencari ke seluruh penjuru untuk menemukan jawaban ini. Terima kasih @ShitalShah
terryg
Bisakah Anda menambahkan penjelasan mengapa penjilidan membantu membuatnya lebih aman?
Steven Lu
Saya buruk ... Saya tidak bermaksud mengatakan itu lebih "lebih aman". Pointer fungsi normal juga aman untuk mengetik tetapi std :: function lebih umum untuk bekerja dengan lambda, penangkapan konteks, metode anggota, dll.
Shital Shah
bind (& MyClass :: afterCompleteCallback, this, std :: placeholder :: _ 1), 2 args untuk 1 dalam definisi, void afterCompleteCallback (hasil float), dapat menjelaskan ini?
nonock
1
@nonock Untuk penunjuk fungsi dari fungsi anggota, kita harus meneruskan penunjuk "ini" sebagai argumen pertama.
sanoj subran
12

std :: bind dipilih ke dalam library setelah proposal untuk menyertakan boost bind, terutama itu adalah spesialisasi fungsi parsial di mana Anda dapat memperbaiki beberapa parameter dan mengubah yang lain dengan cepat. Sekarang ini adalah cara pustaka untuk melakukan lambda di C ++. Seperti yang dijawab oleh Steve Jessop

Sekarang C ++ 11 mendukung fungsi lambda, saya tidak merasakan godaan untuk menggunakan std :: bind lagi. Saya lebih suka menggunakan kari (spesialisasi parsial) dengan fitur bahasa daripada fitur perpustakaan.

Objek std :: function adalah fungsi polimorfik. Ide dasarnya adalah untuk dapat merujuk ke semua objek yang dapat dipanggil secara bergantian.

Saya akan mengarahkan Anda ke dua tautan ini untuk detail lebih lanjut:

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

Entitas yang dapat dipanggil di C ++: http://www.nullptr.me/2011/05/31/callable-entity/#.UJmXuMXA9Z8

Sarang
sumber
5
std::bindtidak pernah ada tanpa lambda - kedua fitur tersebut diperkenalkan di C ++ 11. Kami memang memiliki bind1stdan bind2ndyang merupakan versi kurus dari C ++ 11 bind.
MM
5

Saya menggunakannya lama sekali untuk membuat kumpulan utas plugin di C ++; Karena fungsinya mengambil tiga parameter, Anda dapat menulis seperti ini

Misalkan metode Anda memiliki tanda tangan:

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

Untuk membuat sebuah objek fungsi untuk mengikat ketiga parameter tersebut dapat Anda lakukan seperti ini

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

Sekarang, untuk mengikat parameter, kita harus menulis fungsi pengikat. Jadi, begini:

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

Dan, fungsi pembantu untuk menggunakan kelas 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);
}

dan di sini kami menyebutnya

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

Catatan: f3 (); akan memanggil metode task1-> ThreeParameterTask (21,22,23);

Untuk detail lebih berdarah -> http://www.codeproject.com/Articles/26078/AC-Plug-in-ThreadPool-Design

Alex Punnen
sumber