std :: function และ std :: bind: คืออะไรและควรใช้เมื่อใด


129

ฉันรู้ว่าสิ่ง functors และเมื่อจะใช้พวกเขาด้วยstdอัลกอริทึม แต่ฉันยังไม่เข้าใจสิ่งที่ Stroustrup กล่าวเกี่ยวกับพวกเขาในC ++ 11 คำถามที่พบบ่อย

ทุกคนสามารถอธิบายสิ่งที่std::bindและstd::functionเป็นเมื่อพวกเขาควรจะนำมาใช้และให้ตัวอย่างสำหรับมือใหม่?

คำตอบ:


201

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

นี่เป็นเรื่องที่กระชับกว่าการเขียนคลาส functor เพื่อทำมัน

มีตัวอย่างเพิ่มเติมในบทความที่คุณลิงก์ไป โดยทั่วไปคุณจะใช้เมื่อคุณต้องการส่ง functor ไปยังอัลกอริทึมบางอย่าง คุณมีฟังก์ชันหรือ functor ที่เกือบจะทำงานที่คุณต้องการ แต่สามารถกำหนดค่าได้มากกว่า (กล่าวคือมีพารามิเตอร์มากกว่า) มากกว่าที่อัลกอริทึมใช้ ดังนั้นคุณจึงผูกอาร์กิวเมนต์กับพารามิเตอร์บางตัวและปล่อยส่วนที่เหลือไว้ให้อัลกอริทึมกรอกข้อมูล:

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

เป็นฟังก์ชันน้อยกว่าหรือเท่ากับ (สมมติว่าเป็นคำสั่งรวม blah blah) โดยปกติตัวอย่างนี้ไม่จำเป็นเนื่องจากมีอยู่แล้วstd::less_equal(ใช้ตัว<=ดำเนินการแทน<ดังนั้นหากไม่สอดคล้องกันคุณอาจต้องใช้สิ่งนี้และคุณอาจต้องไปเยี่ยมผู้เขียนของชั้นเรียนด้วยความเฉลียวฉลาด) เป็นการเปลี่ยนแปลงประเภทที่เกิดขึ้นหากคุณใช้รูปแบบการเขียนโปรแกรมเชิงฟังก์ชัน


18
นอกจากนี้ยังสะดวกสำหรับการโทรกลับไปยังฟังก์ชันของสมาชิก:myThread=boost::thread(boost::bind(&MyClass::threadMain, this))
rlduffy

15
คำอธิบายที่ดีของการผูก แต่เรื่องstd::functionอะไร?
RedX

10
powตัวอย่างของคุณไม่ได้รวบรวม เนื่องจากpowเป็นฟังก์ชันโอเวอร์โหลดคุณต้องระบุด้วยตนเองว่าโอเวอร์โหลดใด การผูกไม่สามารถปล่อยให้มันถูกอนุมานโดยผู้เรียกของ functor ที่เป็นผลลัพธ์ เช่นstd::transform(vec.begin(), vec.end(), out.begin(), std::bind((double (*)(double, int))std::pow, _1, 7));
MM

2
อธิบายได้ดีมาก แต่บางครั้งก็std::bindมาพร้อมกับการthisใช้งานเป็นอาร์กิวเมนต์ที่ 2 คุณช่วยอธิบายกรณีการใช้งานนี้ให้ละเอียดได้ไหม
Mendes

2
นอกจากนี้คุณหมายถึง "_1" std::placeholders::_1ด้วย ฉันใช้เวลาสักพักเพื่อหาคำตอบว่าเหตุใดจึงไม่รวบรวม
terryg

26

หนึ่งในการใช้งานหลักของ 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

คุณช่วยเพิ่มคำอธิบายได้ไหมว่าทำไมการผูกจึงช่วยให้ปลอดภัยยิ่งขึ้น
Steven Lu

ฉันไม่ดี ... ฉันไม่ได้ตั้งใจจะบอกว่ามัน "ปลอดภัยกว่า" พอยน์เตอร์ฟังก์ชั่นปกติยังเป็นประเภทที่ปลอดภัย แต่ std :: ฟังก์ชั่นนั้นใช้งานได้ทั่วไปกับ lambdas, การจับบริบท, เมธอดของสมาชิก ฯลฯ
Shital Shah

ผูก (& MyClass :: afterCompleteCallback, นี้, std :: ตัวยึด :: _ 1), 2 args สำหรับ 1 ในคำจำกัดความโมฆะ afterCompleteCallback (ผลลัพธ์ลอย) สามารถอธิบายสิ่งนี้ได้หรือไม่
nonock

1
@nonock สำหรับตัวชี้ฟังก์ชันของฟังก์ชันสมาชิกเราจำเป็นต้องส่งตัวชี้ "this" เป็นอาร์กิวเมนต์แรก
sanoj subran

12

std :: bind ได้รับการโหวตลงในไลบรารีหลังจากข้อเสนอเพื่อรวมการเพิ่มการเชื่อมโยงโดยหลักแล้วมันเป็นความเชี่ยวชาญของฟังก์ชันบางส่วนซึ่งคุณสามารถแก้ไขพารามิเตอร์ไม่กี่ตัวและเปลี่ยนพารามิเตอร์อื่น ๆ ได้ทันที ตอนนี้เป็นวิธีไลบรารีในการทำ lambdas ใน C ++ ตามคำตอบของ Steve Jessop

ตอนนี้ C ++ 11 รองรับฟังก์ชันแลมบ์ดาฉันไม่รู้สึกอยากใช้ std :: bind อีกต่อไป ฉันอยากจะใช้ Currying (ความเชี่ยวชาญเฉพาะบางส่วน) กับคุณสมบัติทางภาษามากกว่าคุณสมบัติของไลบรารี

std :: function objects คือฟังก์ชัน polymorphic แนวคิดพื้นฐานคือสามารถอ้างถึงวัตถุที่เรียกแทนกันได้ทั้งหมด

ฉันจะแนะนำคุณไปที่ลิงก์ทั้งสองนี้เพื่อดูรายละเอียดเพิ่มเติม:

ฟังก์ชัน Lambda ใน 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ไม่เคยมีมาก่อนหากไม่มี lambdas - คุณลักษณะทั้งสองนี้ได้รับการแนะนำใน C ++ 11 เรามีbind1stและbind2ndC ++ 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

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.