การใช้วัตถุมาตรฐานทั่วไป :: ฟังก์ชั่นที่มีฟังก์ชั่นสมาชิกในชั้นหนึ่ง


169

สำหรับคลาสหนึ่งฉันต้องการเก็บพอยน์เตอร์ของฟังก์ชั่นบางส่วนไว้ในฟังก์ชั่นสมาชิกของคลาสเดียวกันในวัตถุที่mapเก็บหนึ่งstd::functionอัน แต่ฉันล้มเหลวในตอนแรกด้วยรหัสนี้:

class Foo {
    public:
        void doSomething() {}
        void bindFunction() {
            // ERROR
            std::function<void(void)> f = &Foo::doSomething;
        }
};

ฉันจะได้รับerror C2064: term does not evaluate to a function taking 0 argumentsในการxxcallobjรวมกับข้อผิดพลาดบาง instantiation แม่แบบแปลก ๆ ขณะนี้ฉันกำลังทำงานกับ Windows 8 ด้วย Visual Studio 2010/2011 และใน Win 7 ด้วย VS10 ก็ล้มเหลวเช่นกัน ข้อผิดพลาดจะต้องขึ้นอยู่กับกฎ C ++ แปลก ๆ ที่ฉันไม่ปฏิบัติตาม

คำตอบ:


301

ฟังก์ชันสมาชิกแบบคงที่จะต้องถูกเรียกด้วยวัตถุ นั่นคือมันจะส่งผ่านตัวชี้ "this" โดยปริยายเสมอ

เนื่องจากstd::functionลายเซ็นของคุณระบุว่าฟังก์ชั่นของคุณไม่มีข้อโต้แย้งใด ๆ ( <void(void)>) คุณต้องผูกข้อโต้แย้งแรก (และข้อเดียว)

std::function<void(void)> f = std::bind(&Foo::doSomething, this);

หากคุณต้องการผูกฟังก์ชันกับพารามิเตอร์คุณต้องระบุตัวแทน:

using namespace std::placeholders;
std::function<void(int,int)> f = std::bind(&Foo::doSomethingArgs, this, std::placeholders::_1, std::placeholders::_2);

หรือถ้าคอมไพเลอร์ของคุณรองรับ lambdas C ++ 11:

std::function<void(int,int)> f = [=](int a, int b) {
    this->doSomethingArgs(a, b);
}

(ฉันไม่มีคอมไพเลอร์ที่มีความสามารถ C ++ 11 อยู่ในมือในขณะนี้ดังนั้นฉันไม่สามารถตรวจสอบได้)


1
ในขณะที่ฉันไม่ได้พึ่งพาการเพิ่มฉันจะใช้แลมบ์ดาแสดงออก;) อย่างไรก็ตามขอบคุณ!
Christian Ivicevic

3
@AlexB: Boost.Bind ไม่ได้ใช้ ADL สำหรับตัวยึดตำแหน่งมันวางไว้ในเนมสเปซที่ไม่ระบุชื่อ
ildjarn

46
ฉันขอแนะนำให้หลีกเลี่ยงการจับภาพรวมทั่วโลก [=] และใช้ [สิ่งนี้] เพื่อให้ชัดเจนยิ่งขึ้นว่าถ่ายอะไรออกไป (Scott Meyers - โมเดิร์น C ++ บทที่ 6 รายการ 31 - หลีกเลี่ยงโหมดจับภาพเริ่มต้น)
Max Raskin

5
เพียงเพิ่มเคล็ดลับเล็กน้อย: ตัวชี้ฟังก์ชันสมาชิกสามารถถูกส่งไปยัง implicitly std::functionพร้อมกับพิเศษthisเนื่องจากเป็นพารามิเตอร์แรกเช่นstd::function<void(Foo*, int, int)> = &Foo::doSomethingArgs
landerlyoung

@landerlyoung: เพิ่มชื่อฟังก์ชั่นพูดว่า "f" ด้านบนเพื่อแก้ไขไวยากรณ์ตัวอย่าง หากคุณไม่ต้องการชื่อคุณสามารถใช้ mem_fn (& Foo :: doSomethingArgs)
Val

80

ไม่ว่าคุณต้องการ

std::function<void(Foo*)> f = &Foo::doSomething;

เพื่อที่คุณจะสามารถเรียกมันได้บนอินสแตนซ์ใด ๆ หรือคุณต้องการผูกอินสแตนซ์ที่เฉพาะเจาะจงเช่น this

std::function<void(void)> f = std::bind(&Foo::doSomething, this);

ขอบคุณสำหรับคำตอบที่ยอดเยี่ยมนี้: D สิ่งที่ฉันต้องการฉันไม่สามารถหาวิธีใช้ฟังก์ชัน std :: เพื่อเรียกใช้ฟังก์ชันสมาชิกในอินสแตนซ์ของคลาสใดก็ได้
penelope

รวบรวมนี้ แต่มันเป็นมาตรฐานหรือไม่ คุณรับประกันหรือไม่ว่าอาร์กิวเมนต์แรกคือthis?
sudo rm -rf slash

@ sudorm-rfslash ใช่คุณคือ
Armen Tsirunyan

ขอบคุณสำหรับการตอบกลับ @ArmenTsirunyan ... ฉันจะหาข้อมูลนี้ได้ที่ไหนในมาตรฐาน?
sudo rm -rf slash

13

หากคุณต้องการเก็บฟังก์ชันของสมาชิกโดยไม่มีอินสแตนซ์ของคลาสคุณสามารถทำสิ่งนี้:

class MyClass
{
public:
    void MemberFunc(int value)
    {
      //do something
    }
};

// Store member function binding
auto callable = std::mem_fn(&MyClass::MemberFunc);

// Call with late supplied 'this'
MyClass myInst;
callable(&myInst, 123);

สิ่งที่จะเป็นรูปลักษณ์ประเภทการจัดเก็บข้อมูลโดยไม่ต้องเหมือนอัตโนมัติ ? บางสิ่งเช่นนี้

std::_Mem_fn_wrap<void,void (__cdecl TestA::*)(int),TestA,int> callable

นอกจากนี้คุณยังสามารถส่งผ่านการจัดเก็บฟังก์ชั่นนี้เพื่อผูกพันฟังก์ชั่นมาตรฐาน

std::function<void(int)> binding = std::bind(callable, &testA, std::placeholders::_1);
binding(123); // Call

บันทึกในอดีตและอนาคต: มีอินเตอร์เฟสเก่ากว่าstd :: mem_funcแต่มีการเลิกใช้แล้ว มีข้อเสนอโพสต์ C ++ 17 เพื่อให้ตัวชี้ไปยังฟังก์ชันสมาชิกเรียกได้ นี่จะยินดีมากที่สุด


@Danh std::mem_fnถูกไม่ถูกลบออก; พวงของเกินพิกัดที่ไม่จำเป็นคือ ในทางตรงกันข้ามstd::mem_funเลิกกับ C ++ 11 และจะถูกลบด้วย C ++ 17
Max Truxa

@Danh นั่นคือสิ่งที่ฉันกำลังพูดถึง;) "พื้นฐาน" เกินพิกัดแรกยังคงมี: template<class R, class T> unspecified mem_fn(R T::*);และมันจะไม่หายไป
Max Truxa

@Danh อ่านDRอย่างระมัดระวัง DR เกิน 12 ใน 13 ถูกนำออกโดย อันสุดท้ายนั้นไม่ใช่ (และจะไม่เป็นเช่นนั้นใน C ++ 11 หรือ C ++ 14)
Max Truxa

1
ทำไมถึงลงคะแนน? การตอบสนองอื่น ๆ กล่าวว่าคุณต้องผูกอินสแตนซ์ของคลาส หากคุณกำลังสร้างระบบที่มีผลผูกพันสำหรับการสะท้อนหรือการเขียนสคริปต์คุณจะไม่ต้องการทำเช่นนั้น วิธีทางเลือกนี้ใช้ได้และมีความเกี่ยวข้องสำหรับบางคน
เกร็ก

ขอบคุณ Danh ฉันได้แก้ไขความคิดเห็นบางส่วนเกี่ยวกับอินเทอร์เฟซในอดีตและอนาคตที่เกี่ยวข้อง
เกร็ก

3

น่าเสียดายที่ C ++ ไม่อนุญาตให้คุณรับวัตถุที่เรียกได้โดยตรงซึ่งอ้างอิงถึงวัตถุและหนึ่งในฟังก์ชันของสมาชิก &Foo::doSomethingให้ "ตัวชี้ไปยังฟังก์ชั่นสมาชิก" ซึ่งหมายถึงฟังก์ชั่นสมาชิก แต่ไม่ใช่วัตถุที่เกี่ยวข้อง

มีสองวิธีในรอบนี้หนึ่งคือการใช้std::bindเพื่อผูก "ตัวชี้ไปยังฟังก์ชั่นสมาชิก" กับthisตัวชี้ อีกอย่างคือการใช้แลมบ์ดาที่จับthisตัวชี้และเรียกใช้ฟังก์ชันสมาชิก

std::function<void(void)> f = std::bind(&Foo::doSomething, this);
std::function<void(void)> g = [this](){doSomething();};

ฉันต้องการที่หลัง

ด้วย g ++ อย่างน้อยการเชื่อมโยงฟังก์ชันสมาชิกจะส่งผลให้วัตถุมีขนาดสามพอยน์เตอร์การกำหนดสิ่งนี้ให้กับstd::functionจะส่งผลให้เกิดการจัดสรรหน่วยความจำแบบไดนามิก

ในทางกลับกันแลมบ์ดาที่จับthisเป็นเพียงตัวชี้ขนาดเดียวการกำหนดให้กับstd::functionจะไม่ส่งผลให้การจัดสรรหน่วยความจำแบบไดนามิกด้วย g ++

ในขณะที่ฉันยังไม่ได้ตรวจสอบสิ่งนี้กับคอมไพเลอร์อื่น ๆ ฉันสงสัยว่าจะพบผลลัพธ์ที่คล้ายกัน


1

คุณสามารถใช้ฟังก์ชั่นถ้าคุณต้องการการควบคุมทั่วไปน้อยลงและแม่นยำมากขึ้นภายใต้ประทุน ตัวอย่างด้วย win32 api ของฉันเพื่อส่งต่อข้อความ api จากคลาสไปยังคลาสอื่น

IListener.h

#include <windows.h>
class IListener { 
    public:
    virtual ~IListener() {}
    virtual LRESULT operator()(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) = 0;
};

Listener.h

#include "IListener.h"
template <typename D> class Listener : public IListener {
    public:
    typedef LRESULT (D::*WMFuncPtr)(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); 

    private:
    D* _instance;
    WMFuncPtr _wmFuncPtr; 

    public:
    virtual ~Listener() {}
    virtual LRESULT operator()(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) override {
        return (_instance->*_wmFuncPtr)(hWnd, uMsg, wParam, lParam);
    }

    Listener(D* instance, WMFuncPtr wmFuncPtr) {
        _instance = instance;
        _wmFuncPtr = wmFuncPtr;
    }
};

Dispatcher.h

#include <map>
#include "Listener.h"

class Dispatcher {
    private:
        //Storage map for message/pointers
        std::map<UINT /*WM_MESSAGE*/, IListener*> _listeners; 

    public:
        virtual ~Dispatcher() { //clear the map }

        //Return a previously registered callable funtion pointer for uMsg.
        IListener* get(UINT uMsg) {
            typename std::map<UINT, IListener*>::iterator itEvt;
            if((itEvt = _listeners.find(uMsg)) == _listeners.end()) {
                return NULL;
            }
            return itEvt->second;
        }

        //Set a member function to receive message. 
        //Example Button->add<MyClass>(WM_COMMAND, this, &MyClass::myfunc);
        template <typename D> void add(UINT uMsg, D* instance, typename Listener<D>::WMFuncPtr wmFuncPtr) {
            _listeners[uMsg] = new Listener<D>(instance, wmFuncPtr);
        }

};

หลักการใช้งาน

class Button {
    public:
    Dispatcher _dispatcher;
    //button window forward all received message to a listener
    LRESULT onMessage(HWND hWnd, UINT uMsg, WPARAM w, LPARAM l) {
        //to return a precise message like WM_CREATE, you have just
        //search it in the map.
        return _dispatcher[uMsg](hWnd, uMsg, w, l);
    }
};

class Myclass {
    Button _button;
    //the listener for Button messages
    LRESULT button_listener(HWND hWnd, UINT uMsg, WPARAM w, LPARAM l) {
        return 0;
    }

    //Register the listener for Button messages
    void initialize() {
        //now all message received from button are forwarded to button_listener function 
       _button._dispatcher.add(WM_CREATE, this, &Myclass::button_listener);
    }
};

ขอให้โชคดีและขอขอบคุณทุกท่านสำหรับการแบ่งปันความรู้


0

คุณสามารถหลีกเลี่ยงstd::bindการทำสิ่งนี้:

std::function<void(void)> f = [this]-> {Foo::doSomething();}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.