सामान्य एसटीडी का उपयोग करना :: एक कक्षा में सदस्य कार्यों के साथ फ़ंक्शन ऑब्जेक्ट


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। वर्तमान में मैं विजुअल स्टूडियो 2010/2011 के साथ विंडोज 8 पर काम कर रहा हूं और वीएस 10 के साथ विन 7 पर यह भी विफल है। त्रुटि कुछ अजीब सी ++ नियमों के आधार पर होनी चाहिए जिसका मैं पालन नहीं करता हूं

जवाबों:


301

एक गैर-स्थैतिक सदस्य फ़ंक्शन को ऑब्जेक्ट के साथ बुलाया जाना चाहिए। यही है, यह हमेशा अपने तर्क के रूप में "इस" सूचक को पारित करता है।

क्योंकि आपका 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);

या, यदि आपका कंपाइलर C ++ 11 लैम्ब्डा का समर्थन करता है:

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

(मैं एक सी ++ हाथ में 11 सक्षम संकलक की जरूरत नहीं है अभी तो मैं इस एक जाँच नहीं कर सकते हैं।)


1
के रूप में मैं बढ़ावा पर निर्भर नहीं हूँ मैं lambda अभिव्यक्ति का उपयोग करेगा;) फिर भी धन्यवाद!
ईसाई आइविसेविच

3
@AlexB: Boost.Bind प्लेसहोल्डर्स के लिए ADL का उपयोग नहीं करता है, यह उन्हें एक अनाम नाम स्थान पर रखता है।
.लडजर्न

46
मैं वैश्विक कैप्चर से बचने के लिए सलाह देता हूं [=] और इसका उपयोग करें [इसे] यह स्पष्ट करने के लिए कि क्या कैप्चर किया गया है (स्कॉट मेयर्स - प्रभावी आधुनिक सी ++ अध्याय 6. आइटम 31 - डिफ़ॉल्ट कैप्चर मोड से बचें)
मैक्स रस्किन

5
बस थोड़ा सा टिप जोड़ें: सदस्य फ़ंक्शन पॉइंटर को std::functionअतिरिक्त thisरूप से डाला जा सकता है , अतिरिक्त के साथ यह पहला पैरामीटर है, जैसेstd::function<void(Foo*, int, int)> = &Foo::doSomethingArgs
लैंडरलॉन्ग

@landerlyoung: फ़ंक्शन सिंटैक्स को ठीक करने के लिए फ़ंक्शन का नाम "f" ऊपर जोड़ें। यदि आपको नाम की आवश्यकता नहीं है तो आप mem_fn (& Foo :: doSomethingArgs) का उपयोग कर सकते हैं।
वैल

80

या तो आप की जरूरत है

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

ताकि आप इसे किसी भी उदाहरण पर कॉल कर सकें, या आपको उदाहरण के लिए एक विशिष्ट उदाहरण को बांधने की आवश्यकता है this

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

इस महान जवाब के लिए धन्यवाद: D बिल्कुल मुझे क्या चाहिए, मुझे नहीं मिला कि मैं किसी भी वर्ग उदाहरण पर एक सदस्य फ़ंक्शन को कॉल करने के लिए एक std :: function कैसे विशेषज्ञ कर सकता हूं।
पेनेलोप

यह संकलन करता है, लेकिन क्या यह मानक है? क्या आप गारंटी देते हैं कि पहला तर्क क्या है this?
सूदो rm -rf स्लैश

@ सुदरम-रफ्लाश हां, आप हैं
अर्मेन त्सिरुयन जूल 9'19

उत्तर के लिए धन्यवाद @ArmenTsirunyan ... मानक में मैं इस जानकारी के लिए कहाँ देख सकता हूँ?
सूडो rm -rf स्लैश

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 मौजूद था, लेकिन तब से इसे हटा दिया गया है। एक प्रस्ताव मौजूद है, सी + + 17 पोस्ट करने के लिए, सदस्य के कार्यों के लिए सूचक को कॉल करने योग्य बनाने के लिए । यह सबसे स्वागत योग्य होगा।


@ दान std::mem_fnको हटाया नहीं गया था ; अनावश्यक ओवरलोड का एक गुच्छा था। दूसरी ओर std::mem_funC ++ 11 के साथ पदावनत किया गया था और C ++ 17 के साथ हटा दिया जाएगा।
मैक्स Truxa

@ दान यह ठीक है कि मैं किस बारे में बात कर रहा हूं;) बहुत पहले "बुनियादी" अधिभार अभी भी है: template<class R, class T> unspecified mem_fn(R T::*);और यह दूर नहीं जाएगा।
मैक्स ट्रूसा

@ दान पढ़ें डीआर ध्यान से। 13 में से 12 ओवरलोड को डीआर ने हटा दिया। वह अंतिम एक नहीं थी (और नहीं होगी; न ही C ++ 11 या C ++ 14 में)।
मैक्स 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();};

मैं बाद वाला पसंद करूंगा।

जी ++ के साथ कम से कम एक सदस्य फ़ंक्शन को बाध्य करने के परिणामस्वरूप आकार में एक ऑब्जेक्ट तीन-पॉइंटर्स std::functionहोगा , इसे असाइन करने से गतिशील मेमोरी आवंटन होगा।

दूसरी ओर, एक लैम्ब्डा जो कैप्चर करता thisहै, वह आकार में केवल एक पॉइंटर है, इसे std::functionवसीयत में निर्दिष्ट करने से जी ++ के साथ गतिशील मेमोरी आवंटन नहीं होगा।

जबकि मैंने इसे अन्य संकलक के साथ सत्यापित नहीं किया है, मुझे संदेह है कि इसी तरह के परिणाम वहां पाए जाएंगे।


1

यदि आप हुड के नीचे कम सामान्य और अधिक सटीक नियंत्रण चाहते हैं तो आप फंक्शनलर्स का उपयोग कर सकते हैं। एक वर्ग से दूसरे वर्ग में एपीआई संदेश को अग्रेषित करने के लिए मेरी win32 एपीआई के साथ उदाहरण।

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();}
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.