2012-11-29 16 views
9

OO में, एक आम तौर पर इंटरफेस के साथ कॉलबैक लागू करता है: (किसी न किसी उदाहरण)इंटरफेस या फ़ंक्शन ऑब्जेक्ट्स के साथ कॉलबैक?

class Message {} 

class IMsgProcessor { 
public: 
    virtual void handle_msg(const Message& msg) = 0; 
} 

class RequestMsgProcessor : public IMsgProcessor { 
    virtual void handle_msg(const Message& msg) { 
    // process request message 
    } 
} 

class CustomSocket { 
public: 
    Socket(IMsgProcessor* p) : processor_(p) {} 

    void receive_message_from_network(const Message& msg) { 
     // processor_ does implement handle_msg. Otherwise a compile time error. 
     // So we've got a safe design. 
     processor_->handle_msg(msg); 
    } 
private: 
    IMsgProcessor* processor_; 
} 

अब तक तो अच्छा। सी ++ 11 के साथ, ऐसा करने का एक और तरीका है कस्टमसॉकेट को केवल std :: function ऑब्जेक्ट का एक उदाहरण प्राप्त होता है। यह परवाह नहीं करता है, जहां यह लागू किया गया है या यहां तक ​​कि वस्तु अगर एक गैर शून्य मान है:
1. क्या प्रदर्शन प्रभावों के बारे में:

class CustomSocket { 
public: 
    Socket(std::function<void(const Message&)>&& f) : func_(std:forward(f)) {} 

    void receive_message_from_network(const Message& msg) { 
     // unfortunately we have to do this check for every msg. 
     // or maybe not ... 
     if(func_) 
      func_(msg); 
    } 
private: 
    std::function<void(const Message&)> func_; 
} 

अब यहाँ सवाल कर रहे हैं? मैं अनुमान लगा रहा हूं कि एक वर्चुअल फ़ंक्शन कॉल फ़ंक्शन ऑब्जेक्ट को कॉल करने से तेज़ है लेकिन कितनी तेज़ है? मैं एक तेज संदेश प्रणाली को कार्यान्वित कर रहा हूं और मैं किसी भी अनावश्यक प्रदर्शन दंड से बचना चाहता हूं।
2. सॉफ्टवेयर इंजीनियरिंग प्रथाओं के संदर्भ में, मुझे कहना है कि मुझे दूसरा दृष्टिकोण बेहतर पसंद है। कम कोड, कम फ़ाइलें, कम अव्यवस्था: कोई इंटरफ़ेस वर्ग नहीं। अधिक लचीलापन: आप केवल कुछ फ़ंक्शन ऑब्जेक्ट्स सेट करके और दूसरों को शून्य छोड़कर इंटरफ़ेस का सबसेट लागू कर सकते हैं। या आप अलग-अलग वर्गों में या मुक्त कार्यों या दोनों के संयोजन (एक एकल उपclass के बजाय) में लागू इंटरफ़ेस के विभिन्न हिस्सों को प्राप्त कर सकते हैं। इसके अलावा, कस्टमसॉकेट किसी भी वर्ग द्वारा आईएमएसएसप्रोसेसर के उप-वर्गों का उपयोग नहीं किया जा सकता है। यह मेरी राय में शानदार लाभ है।
आप क्या कहते हैं? क्या आप इन तर्कों में कोई मौलिक दोष देखते हैं?

+0

रूप में एक ही क्षमता है '// दुर्भाग्य से हम हर msg.' के लिए इस चेक करना है - एक ही बात इंटरफ़ेस कोड के लिए सच होना चाहिए। आप कभी भी जांच नहीं करते कि क्या आप एक नल पॉइंटर पास नहीं करते हैं ... – Xeo

+0

प्रतिरूप के बारे में: आभासी func मूल रूप से एक टेबल लुकअप जोड़ता है, std :: फ़ंक्शन एक विशेष एडाप्टर को कॉल अग्रेषित करता है। यदि आपका फ़ंक्शन/विधि वर्चुअल नहीं है तो इसे तेज़ होने के लिए अनुकूलित किया जा सकता है। बेंचमार्क करना है। यदि आप टेम्पलेट तर्क के साथ 'std :: function' को प्रतिस्थापित करते हैं तो आप इसे वर्चुअल कॉल से तेज़ी से बना सकते हैं। – Antoine

+0

@Xeo जरूरी नहीं: इंटरफ़ेस कोड में मैं केवल कन्स्ट्रक्टर में जांच करूंगा कि पास ऑब्जेक्ट न्यूल नहीं है - एक पूर्ण स्वीकार करने से कोई अर्थ नहीं होता है। लेकिन दूसरे मामले में क्योंकि आप इंटरफ़ेस का आंशिक कार्यान्वयन कर सकते हैं (यानी, केवल कुछ फ़ंक्शन ऑब्जेक्ट्स नॉन-नल) आपको चेक की आवश्यकता है। – PoP

उत्तर

3

आप दोनों दुनिया के सर्वश्रेष्ठ

template<class F> 
class MsgProcessorT:public IMsgProcessor{ 
    F f_; 
    public: 
    MsgProcessorT(F f):f_(f){} 
    virtual void handle_msg(const Message& msg) { 
     f_(msg); 
} 

}; 
template<class F> 
IMsgProcessor* CreateMessageProcessor(F f){ 
    return new MsgProcessor<T>(f); 

}; 

तो फिर तुम इस

Socket s(CreateMessageProcessor([](const Message& msg){...})); 

की तरह उपयोग कर सकते हैं या हो सकता है यह और भी आसान सॉकेट एक और निर्माता जोड़ने बनाने के लिए

class Socket{ 
... 
template<class F> 
Socket(F f):processor_(CreateMessageProcessor(f){} 


}; 

फिर आप

कर सकते हैं
Socket s([](const Message& msg){...}); 

और फिर भी एक आभासी समारोह कॉल

+4

ईडब्ल्यू, नग्न 'नया'! अपनी शर्म आओ! ... एक और गंभीर नोट पर, 'std :: function' आंतरिक रूप से अंतर्निहित वर्चुअल प्रेषण या फ़ंक्शन पॉइंटर्स, 'शून्य *' और फ़ंक्शन टेम्पलेट्स के साथ मैन्युअल कार्यान्वयन का उपयोग करता है। – Xeo

+0

@Xeo आप सही हैं। मैं सॉकेट को गैर-प्रतिलिपि बनाउंगा और processor_ के लिए unique_ptr का उपयोग करूंगा और CreateMessageProcessor से एक अद्वितीय_ptr वापस कर दूंगा। –

+0

@Xeo: हाहा, जिसने मुझे क्रैक किया। : -] – ildjarn

2

इंटरफ़ेस दृष्टिकोण अधिक पारंपरिक है लेकिन अधिक वर्बोज़: यह स्पष्ट है कि MessageProcessor क्या करता है और आपको इसे जांचना नहीं है। इसके अलावा, आप कई सॉकेट के साथ एक और एक ही वस्तु का दोबारा उपयोग कर सकते हैं।

std::function दृष्टिकोण अधिक सामान्य है: operator()(Message const&) स्वीकार करने वाला कुछ भी उपयोग किया जा सकता है। हालांकि, verbosity की कमी कोड को कम पठनीय बनाने का खतरा है।

मुझे प्रदर्शन दंड के बारे में पता नहीं है, लेकिन अगर महत्वपूर्ण अंतर हैं तो आश्चर्यचकित होगा।

यदि यह आपके कोड का एक अनिवार्य हिस्सा है (जैसा कि ऐसा लगता है) मैं इंटरफ़ेस दृष्टिकोण से चिपके रहूंगा।

1

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

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