std :: function và std :: bind: chúng là gì và khi nào thì nên sử dụng?


129

Tôi biết các chức năng là gì và khi nào sử dụng chúng với các stdthuật toán, nhưng tôi chưa hiểu Stroustrup nói gì về chúng trong Câu hỏi thường gặp về C ++ 11 .

Bất cứ ai có thể giải thích những gì std::bindstd::functionlà gì , khi nào chúng nên được sử dụng và đưa ra một số ví dụ cho người mới?

Câu trả lời:


201

std::bindlà dành cho ứng dụng chức năng từng phần .

Nghĩa là, giả sử bạn có một đối tượng hàm fcó 3 đối số:

f(a,b,c);

Bạn muốn một đối tượng hàm mới chỉ nhận hai đối số, được định nghĩa là:

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

glà một "ứng dụng một phần" của hàm f: đối số giữa đã được chỉ định và còn lại hai đối số.

Bạn có thể sử dụng std::bindđể nhận g:

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

Điều này ngắn gọn hơn là thực sự viết một lớp functor để làm điều đó.

Có các ví dụ khác trong bài viết mà bạn liên kết đến. Bạn thường sử dụng nó khi bạn cần chuyển một hàm cho một số thuật toán. Bạn có một hàm hoặc bộ chức năng gần như thực hiện công việc bạn muốn, nhưng có thể cấu hình nhiều hơn (tức là có nhiều tham số hơn) so với thuật toán sử dụng. Vì vậy, bạn liên kết các đối số với một số tham số và để phần còn lại cho thuật toán điền vào:

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

Ở đây, powcó hai tham số và có thể nâng lên bất kỳ sức mạnh nào , nhưng tất cả những gì chúng ta quan tâm là nâng lên sức mạnh của 7.

Như một cách sử dụng không thường xuyên không phải là ứng dụng hàm một phần, bindcũng có thể sắp xếp lại thứ tự các đối số cho một hàm:

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

Tôi không khuyên bạn nên sử dụng nó chỉ vì bạn không thích API, nhưng nó có những ứng dụng thực tế tiềm năng, ví dụ như vì:

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

là một hàm nhỏ hơn hoặc bằng (giả sử một thứ tự tổng, blah blah). Ví dụ này thường không cần thiết vì đã có một std::less_equal(nó sử dụng <=toán tử thay vì sử dụng <, vì vậy nếu chúng không nhất quán thì bạn có thể cần cái này và bạn cũng có thể cần đến thăm tác giả của lớp bằng một đầu mối). Tuy nhiên, đó là kiểu chuyển đổi xuất hiện nếu bạn đang sử dụng một kiểu lập trình chức năng.


18
Cũng tiện dụng cho callbacks chức năng thành viên:myThread=boost::thread(boost::bind(&MyClass::threadMain, this))
rlduffy

15
Giải thích tốt về ràng buộc. Nhưng những gì về std::function?
RedX

10
powVí dụ của bạn không biên dịch. Vì powlà một hàm quá tải, bạn phải chỉ định thủ công quá tải nào. Ràng buộc không thể để nó được suy ra bởi người gọi của hàm kết quả. Ví dụstd::transform(vec.begin(), vec.end(), out.begin(), std::bind((double (*)(double, int))std::pow, _1, 7));
MM

2
Được giải thích rất tốt, nhưng đôi khi std::bindđi kèm với thiscách sử dụng như đối số thứ hai. Bạn có thể vui lòng giải thích trường hợp sử dụng này không?
Mendes

2
Cũng bởi "_1" bạn có nghĩa là std::placeholders::_1. Tôi đã mất một lúc để tìm hiểu lý do tại sao điều này không được biên dịch.
terryg 20/1218

26

Một trong những cách sử dụng chính của std :: function và std :: bind là nhiều con trỏ hàm được tạo ra hơn. Bạn có thể sử dụng nó để triển khai cơ chế gọi lại. Một trong những tình huống phổ biến là bạn có một số hàm sẽ mất nhiều thời gian để thực thi nhưng bạn không muốn đợi nó trả về thì bạn có thể chạy hàm đó trên luồng riêng và cung cấp cho nó một con trỏ hàm mà nó sẽ gọi lại sau khi hoàn tất.

Đây là mã mẫu về cách sử dụng mã này:

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
Đây là một câu trả lời tuyệt vời. Tôi đã xem xét tất cả để tìm câu trả lời này. Cảm ơn @ShitalShah
terryg 20/12/18

Bạn có thể thêm lời giải thích cho lý do tại sao ràng buộc giúp làm cho nó an toàn hơn không?
Steven Lu

Tệ hại của tôi ... Tôi không định nói nó là "an toàn hơn". Con trỏ chức năng bình thường cũng được typesafe tuy nhiên std :: chức năng là chung chung hơn để làm việc với lambdas, chụp bối cảnh, phương pháp thành viên, vv
Shital Shah

bind (& MyClass :: afterCompleteCallback, this, std :: placeholder :: _ 1), 2 args cho 1 trong định nghĩa, void afterCompleteCallback (float result), có thể giải thích điều này không?
nonock

1
@nonock Đối với con trỏ hàm của các hàm thành viên, chúng ta cần chuyển con trỏ "this" làm đối số đầu tiên.
sanoj subran

12

std :: bind đã được bình chọn vào thư viện sau khi đề xuất bao gồm boost bind, chủ yếu nó là chuyên môn hóa một phần chức năng trong đó bạn có thể sửa một vài tham số và thay đổi những tham số khác một cách nhanh chóng. Bây giờ đây là cách thư viện để thực hiện lambdas trong C ++. Như câu trả lời của Steve Jessop

Bây giờ C ++ 11 hỗ trợ các hàm lambda, tôi không cảm thấy bị cám dỗ khi sử dụng std :: bind nữa. Tôi muốn sử dụng tính năng currying (chuyên môn hóa một phần) với tính năng ngôn ngữ hơn là tính năng thư viện.

Đối tượng hàm std :: là các hàm đa hình. Ý tưởng cơ bản là có thể tham chiếu đến tất cả các đối tượng có thể gọi thay thế cho nhau.

Tôi sẽ chỉ bạn đến hai liên kết này để biết thêm chi tiết:

Các hàm Lambda trong C ++ 11: http://www.nullptr.me/2011/10/12/c11-lambda-having-fun-with-brackets/#.UJmXu8XA9Z8

Thực thể có thể gọi trong C ++: http://www.nullptr.me/2011/05/31/callable-entity/#.UJmXuMXA9Z8


5
std::bindchưa bao giờ tồn tại mà không có lambdas - cả hai tính năng đó đều được giới thiệu trong C ++ 11. Chúng tôi đã có bind1stbind2ndđó là các phiên bản khác của C ++ 11 bind.
MM

5

Tôi đã sử dụng nó từ lâu để tạo một nhóm chủ đề plugin trong C ++; Vì hàm nhận ba tham số nên bạn có thể viết như thế này

Giả sử phương thức của bạn có chữ ký:

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

Để tạo một đối tượng hàm để liên kết ba tham số, bạn có thể làm như thế này

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

Bây giờ, để ràng buộc các tham số, chúng ta phải viết một hàm kết dính. Vì vậy, đây là nó:

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

Và, một hàm trợ giúp để sử dụng lớp 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);
}

và ở đây chúng tôi làm thế nào để gọi nó

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

Lưu ý: f3 (); sẽ gọi phương thức task1-> ThreeParameterTask (21,22,23);

Để biết thêm thông tin chi tiết -> http://www.codeproject.com/Articles/26078/AC-Plug-in-ThreadPool-Design

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.