2009-03-14 15 views
7

मुझे एक कक्षा द्वारा परिभाषित एक वर्ग द्वारा परिभाषित एक सुपर क्लास निष्पादित कॉलबैक करने में सक्षम होने की आवश्यकता है। मैं सी ++ के लिए अपेक्षाकृत नया हूं और जो कुछ मैं कह सकता हूं उससे यह दिखता है कि सदस्य-फ़ंक्शन-पॉइंटर्स का विषय एक बहुत ही अस्पष्ट क्षेत्र है।सी ++ प्वाइंटर्स सदस्य कार्य विरासत

मैंने प्रश्नों और यादृच्छिक ब्लॉग पोस्टों के उत्तर देखे हैं जो सभी प्रकार की चीजों पर चर्चा करते हैं, लेकिन मुझे यकीन नहीं है कि उनमें से कोई भी मेरे प्रश्न से विशेष रूप से व्यवहार कर रहा है या नहीं।

यहां कोड का एक साधारण हिस्सा है जो दिखाता है कि मैं क्या करने की कोशिश कर रहा हूं। उदाहरण शायद बहुत समझ में नहीं आता है, लेकिन यह उस कोड जैसा सटीक रूप से दिखता है जिसे मैं लिखने की कोशिश कर रहा हूं।

class A { 
    protected: 
    void doSomething(void (A::*someCallback)(int a)) { 
     (*this.*someCallback)(1234); 
    } 
}; 

class B : public A { 
    public: 
    void runDoIt() { doSomething(&B::doIt); } 
    void runDoSomethingElse() { doSomething(&B::doSomethingElse); } 
    protected: 
    void doIt(int foo) { 
     cout << "Do It! [" << foo << "]\n"; 
    } 
    void doSomethingElse(int foo) { 
     cout << "Do Something Else! [" << foo << "]\n"; 
    } 
}; 

int main(int argc, char *argv[]) { 
    B b; 
    b.runDoIt(); 
    b.runDoSomethingElse(); 
} 

उत्तर

7

यदि आप बूस्ट लाइब्रेरीज़ का उपयोग कर सकते हैं, तो मैं सुझाव दूंगा कि आप कार्य के लिए boost :: function का उपयोग करें।

class A { 
public: 
    void doSomething(boost::function< void (int) > callback) 
    { 
     callback(5); 
    } 
}; 

तो किसी भी इनहेरीट (या बाहरी वर्ग) को बढ़ावा देने :: बाँध उपयोग कर सकते हैं एक फोन करना है:

class B { 
public: 
    void my_method(int a); 
}; 
void test() 
{ 
    B b; 
    A a; 
    a.doSomething(boost::bind(&B::my_method, &b, _1)); 
}; 

मैं सटीक सिंटैक्स की जाँच नहीं की है और मैं के ऊपर से यह लिख चुके हैं मेरी सिर, लेकिन यह कम से कम उचित कोड के करीब है।

+0

मैं इस दृष्टिकोण को दूसरा करूंगा। यहां बढ़ावा देने का उपयोग बहुत समझ में आता है, उन्होंने इस तरह की चीज में बहुत सारे प्रयास किए हैं। – sstock

+0

मैं कुछ भी बढ़ावा देने के लिए थोड़ा संकोच करता था, लेकिन मैं इसे आज़माने वाला हूं। जैसा कि आपने यहां वर्णित किया है, यह कम या ज्यादा काम करता है! –

0

आप विधिबद्ध वर्ग के रूप में व्युत्पन्न कक्षा में परिभाषित विधि को कॉल करने का प्रयास कर रहे हैं।
लेकिन यह फ़ंक्शन बेस क्लास में परिभाषित नहीं है।

2

सी ++ आपके उद्देश्य के लिए सदस्य फ़ंक्शन पॉइंटर्स की तुलना में बेहतर संरचना प्रदान करता है। (आम तौर पर, आपको उचित रूप से लिखित शुद्ध सी ++ में फ़ंक्शन पॉइंटर्स का उपयोग नहीं करना चाहिए)। मेरे सिर के शीर्ष से दो विकल्प:

1) एक कॉल किए गए doIt() पर शुद्ध वर्चुअल फ़ंक्शन बनाएं। DoSomething() से कॉल करें()। बी पर, यह इंगित करने के लिए एक enum सदस्य चर रखें कि आप क्या करना चाहते हैं, और इसे कॉल करने से पहले इसे सेट करें कुछ()। बी में ओवरआईड() में बी को ओवरराइड करें, इसके कार्यान्वयन के साथ सदस्य अंतर्निहित कोड को उस अंतर्निहित कोड पर चलाएं जिसे आप चलाने के लिए चाहते हैं।

2) एक एकल DoIt() शुद्ध आभासी विधि के साथ एक DoInterface वर्ग बनाएँ। अपने क्लास बी के लिए इसका व्युत्पन्न बनाएं जिसमें मालिक बी के उदाहरण के लिए एक पॉइंटर है और अपने प्रत्येक DoInterface कार्यान्वयन में बी पर उचित फ़ंक्शन को कॉल करने के लिए doIt() लागू करें। doSomething() एक पैरामीटर के रूप में DoInterface का एक उदाहरण लेगा। इसे कॉलबैक-ऑब्जेक्ट पैटर्न कहा जाता है।

+0

विकल्प 1, वर्चुअल सदस्य फ़ंक्शंस, ऐसा करने का सबसे आसान और सबसे सुरुचिपूर्ण तरीका है, एक चेतावनी के साथ: वर्चुअल सदस्यों को बेस क्लास कन्स्ट्रक्टर या विनाशकों से नहीं कहा जा सकता है। लेकिन जब तक आपको ऐसा करने की आवश्यकता नहीं है, तब तक जाने का रास्ता है। –

+0

अगर मुझे केवल एक विधि के बारे में चिंता करनी पड़ी, या मुझे पता था कि मैं "कितना" करना चाहता हूं, तो यह काम करेगा। मैं इसे और अधिक बारीकी से देखूंगा लेकिन मुझे नहीं लगता कि यह मेरे उद्देश्यों के लिए काम करेगा क्योंकि मैं पहले से ही बहुत कुछ कर रहा था। –

+0

बी बी का एक व्युत्पन्न वर्ग बी बी का मुद्दा है, यह जानता है कि कितने तरीकों से यह "किया जा सकता है" और जानता है कि कौन से किए जाने चाहिए, इसे केवल ए से ट्रिगर की आवश्यकता होती है, जो कि बिंदु है बी –

8

समस्या यह है कि B का सदस्य फ़ंक्शन A का सदस्य फ़ंक्शन नहीं है, भले ही BA से निकला है। यदि आपके पास void (A::*)() है, तो आप ऑब्जेक्ट के वास्तविक व्युत्पन्न प्रकार की परवाह किए बिना, किसी भी A * पर इसका आह्वान कर सकते हैं।

(इसी सिद्धांत निश्चित रूप से भी एक A & पर लागू होगा।)

मान लें BA से निकला है, और CA से निकला है; यह एक void (B::*)() (माना) एक void (A::*)() के रूप में विचार करने के लिए संभव थे थे, एक कुछ इस तरह करने में सक्षम हो जाएगा:

A *c=new C; 
A *b=new B; 
void (A::*f)()=&B::fn;//fn is not defined in A 
(c->*f)(); 

और B के सदस्य समारोह प्रकार C की एक वस्तु पर कहा जा सकता है। परिणाम सबसे अच्छा अप्रत्याशित होगा।

class Callback { 
public: 
    virtual ~Callback() { 
    } 

    virtual Do(int a)=0; 
}; 

तब समारोह है कि कॉलबैक कॉल में से एक लेता है:

उदाहरण कोड के आधार पर, और बढ़ावा तरह कुछ की गैर-उपयोग यह सोचते हैं, मैं एक वस्तु के रूप कॉलबैक की संरचना करने के इच्छुक होगी इन वस्तुओं किसी भी तरह के बजाय एक सादे समारोह सूचक:

class A { 
protected: 
    void doSomething(Callback *c) { 
     c->Do(1234); 
    } 
}; 

फिर आप व्युत्पन्न समारोह आपको बुला रहा में रुचि रखते हैं प्रति एक कॉलबैक हो सकता था। छदाम, उदाहरण के लिए के लिए:

class B:public A { 
public: 
    void runDoIt() { 
     DoItCallback cb(this); 
     this->doSomething(&cb); 
    } 
protected: 
    void doIt(int foo) { 
     // whatever 
    } 
private: 
    class DoItCallback:public Callback { 
    public: 
     DoItCallback(B *b):b_(b) {} 

     void Do(int a) { 
      b_->doIt(a); 
     } 
    private: 
     B *b_; 
    }; 
}; 

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

class BCallback:public Callback { 
public: 
    BCallback(B *obj,void (B::*fn)(int)):obj_(obj),fn_(fn) {} 

    void Do(int a) { 
     (obj_->*fn_)(a); 
    } 
private: 
    B *obj_; 
    void (B::*fn_)(int); 
}; 

यह इस तरह छदाम बनाना होगा:

void B::runDoIt() { 
    BCallback cb(this,&B::doIt); 
    this->doSomething(&cb); 
} 

यह संभवतः "सुधार" किया जा सकता है, हालांकि नहीं सभी पाठकों यह काफी है कि रास्ते में देख सकते हैं, यह templating द्वारा:

template<class T> 
class GenericCallback:public Callback { 
public: 
    GenericCallback(T *obj,void (T::*fn)(int)):obj_(obj),fn_(fn) {} 

    void Do(int a) { 
     (obj_->*fn_)(a); 
    } 
private: 
    T *obj_; 
    void (T::*fn_)(int); 
}; 

इस का उपयोग करना, ऊपर runDoIt समारोह बन सकता है:

void B::runDoIt() { 
    GenericCallback<B> cb(this,&B::doIt); 
    this->doSomething(&cb); 
} 

(जेनेरिक कॉलबैक सदस्य फ़ंक्शन पॉइंटर पर भी टेम्पलेट किया जा सकता है, हालांकि अधिकांश मामलों में यह कोई व्यावहारिक लाभ प्रदान करने की संभावना नहीं है। यह सिर्फ और टाइपिंग है।)

मुझे अच्छी तरह से बाहर निकलने के लिए इस तरह की संरचनाएं मिल रही हैं, क्योंकि इसे विरासत की आवश्यकता नहीं है। इसलिए इस कोड पर कुछ प्रतिबंध लगाए जाते हैं जिन्हें वापस बुलाया जाना है, जो कि मेरे दिमाग में हमेशा एक अच्छी बात है, और मैंने पाया है कि पूरी तरह से खत्म करने के लिए कठिन होने वाली क्रियापदता से अधिक है। उदाहरण के आधार पर असंभव यह बताने के लिए कि क्या यह दृष्टिकोण वास्तव में उपयुक्त होगा, हालांकि ...

+0

पर ओवरराइड डूआईटी() मैं इसे इस तरह से करने की कोशिश करने जा रहा हूं। मैं उम्मीद कर रहा था की तुलना में यह अधिक verbose होगा, लेकिन मुझे लगता है कि यह अगले सबसे अच्छे समाधान से बेहतर होगा जिसमें आवरण और स्विचिंग शामिल है। –

+0

सीखने के लिए बहुत अच्छा जवाब है। आपके द्वारा दिया गया दृष्टिकोण बूस्ट libs के सीमित संस्करण के करीब है। कॉलबैक बूस्ट :: फ़ंक्शन का सीमित संस्करण है और जेनेरिककॉलबैक बाइंड का एक कम संस्करण है। –

+0

एक और बात यह है कि आप कुछ पॉइंटर्स को हटा सकते हैं और उन्हें संदर्भों में अनुवाद कर सकते हैं।doSomething (कॉलबैक और सी), या जेनेरिककॉलबैक एक पॉइंटर के बजाय एक और स्टोर कर सकता है, जिससे यह अधिक स्पष्ट हो जाता है कि यह उस स्मृति से निपट नहीं पाएगा –

संबंधित मुद्दे