C ++ में कॉलबैक फ़ंक्शन लागू करते समय, क्या मुझे अभी भी C-style फ़ंक्शन पॉइंटर का उपयोग करना चाहिए:
void (*callbackFunc)(int);
या मुझे std :: function का उपयोग करना चाहिए:
std::function< void(int) > callbackFunc;
C ++ में कॉलबैक फ़ंक्शन लागू करते समय, क्या मुझे अभी भी C-style फ़ंक्शन पॉइंटर का उपयोग करना चाहिए:
void (*callbackFunc)(int);
या मुझे std :: function का उपयोग करना चाहिए:
std::function< void(int) > callbackFunc;
जवाबों:
संक्षेप में,std::function
तब तक उपयोग करें जब तक आपके पास कारण नहीं है।
फंक्शन पॉइंटर्स का नुकसान होता है कि वे किसी संदर्भ को कैप्चर नहीं कर पाते हैं । आप उदाहरण के लिए एक कॉलबैक के रूप में एक लैम्ब्डा फ़ंक्शन पास करने में सक्षम नहीं होंगे जो कुछ संदर्भ चर को कैप्चर करता है (लेकिन यह काम करेगा यदि यह किसी पर कब्जा नहीं करता है)। किसी वस्तु के सदस्य चर (यानी गैर-स्थिर) को कॉल करना इस प्रकार भी संभव नहीं है, क्योंकि ऑब्जेक्ट ( this
-पॉइंट्टर) को कैप्चर करने की आवश्यकता होती है। (1)
std::function
(चूंकि सी ++ 11) मुख्य रूप से एक फ़ंक्शन को संग्रहीत करने के लिए है (इसे चारों ओर से गुजरना आवश्यक नहीं है कि इसे संग्रहीत किया जाए)। इसलिए यदि आप किसी सदस्य चर में उदाहरण के लिए कॉलबैक को स्टोर करना चाहते हैं, तो यह संभवतः आपका सबसे अच्छा विकल्प है। लेकिन अगर आप इसे स्टोर नहीं करते हैं, तो यह एक अच्छी "पहली पसंद" है, हालांकि इसमें कुछ (बहुत छोटे) ओवरहेड को शुरू करने का नुकसान होता है जब बुलाया जा रहा है (इसलिए बहुत प्रदर्शन-महत्वपूर्ण स्थिति में यह एक समस्या हो सकती है लेकिन अधिकांश में यह नहीं होना चाहिए)। यह बहुत ही "सार्वभौमिक" है: यदि आप सुसंगत और पठनीय कोड के बारे में बहुत परवाह करते हैं और साथ ही आपके द्वारा किए जाने वाले प्रत्येक विकल्प के बारे में सोचना नहीं चाहते हैं (यानी इसे सरल रखना चाहते हैं), std::function
आपके द्वारा पास होने वाले प्रत्येक फ़ंक्शन के लिए उपयोग करें ।
एक तीसरे विकल्प के बारे में सोचें: यदि आप एक छोटे से फंक्शन को लागू करने वाले हैं, जो तब दिए गए कॉलबैक फ़ंक्शन के माध्यम से कुछ रिपोर्ट करता है, तो एक टेम्प्लेट पैरामीटर पर विचार करें , जो तब कोई कॉल करने योग्य ऑब्जेक्ट हो सकता है , यानी एक फंक्शन पॉइंटर, एक फनकार, एक लैम्डा, ए std::function
, ... यहां ड्राबैक यह है कि आपका (बाहरी) फंक्शन एक टेम्प्लेट बन जाता है और इसलिए हेडर में इसे लागू करना होता है। दूसरी तरफ आपको यह फायदा मिलता है कि कॉलबैक में कॉल इनबिल्ट हो सकती है, क्योंकि आपके (बाहरी) फंक्शन का क्लाइंट कोड "देखता है" कॉलबैक के लिए कॉल सटीक प्रकार की जानकारी उपलब्ध होगी।
टेम्पलेट पैरामीटर के साथ संस्करण के लिए उदाहरण ( प्री-सी ++ 11 के &
बजाय लिखें &&
):
template <typename CallbackFunction>
void myFunction(..., CallbackFunction && callback) {
...
callback(...);
...
}
जैसा कि आप निम्न तालिका में देख सकते हैं, उनमें से सभी के अपने फायदे और नुकसान हैं:
+-------------------+--------------+---------------+----------------+
| | function ptr | std::function | template param |
+===================+==============+===============+================+
| can capture | no(1) | yes | yes |
| context variables | | | |
+-------------------+--------------+---------------+----------------+
| no call overhead | yes | no | yes |
| (see comments) | | | |
+-------------------+--------------+---------------+----------------+
| can be inlined | no | no | yes |
| (see comments) | | | |
+-------------------+--------------+---------------+----------------+
| can be stored | yes | yes | no(2) |
| in class member | | | |
+-------------------+--------------+---------------+----------------+
| can be implemented| yes | yes | no |
| outside of header | | | |
+-------------------+--------------+---------------+----------------+
| supported without | yes | no(3) | yes |
| C++11 standard | | | |
+-------------------+--------------+---------------+----------------+
| nicely readable | no | yes | (yes) |
| (my opinion) | (ugly type) | | |
+-------------------+--------------+---------------+----------------+
(1) वर्कअराउंड इस सीमा को पार करने के लिए मौजूद है, उदाहरण के लिए आपके (बाहरी) फ़ंक्शन को अतिरिक्त पैरामीटर के रूप में अतिरिक्त डेटा पास करना: myFunction(..., callback, data)
कॉल करेगा callback(data)
। वह सी-स्टाइल "दलीलों के साथ कॉलबैक" है, जो सी ++ में संभव है (और जिस तरह से विन 32 एपीआई में उपयोग किया जाता है) लेकिन हमें सी ++ में बेहतर विकल्प होने से बचना चाहिए।
(2) जब तक हम क्लास टेम्पलेट के बारे में बात नहीं कर रहे हैं, यानी जिस क्लास में आप फंक्शन स्टोर करते हैं, वह टेम्प्लेट है। लेकिन इसका मतलब यह होगा कि क्लाइंट की तरफ से फंक्शन का प्रकार उस ऑब्जेक्ट के प्रकार को तय करता है जो कॉलबैक को स्टोर करता है, जो वास्तविक उपयोग के मामलों के लिए लगभग कभी विकल्प नहीं होता है।
(3) प्री-सी ++ 11 के लिए, उपयोग करें boost::function
std::function
टाइप-मिटाने के लिए परिवर्तित किया जा सकता है यदि आपको इसे तुरंत स्टोर करने की आवश्यकता है संदर्भ कहा जाता है)।
void (*callbackFunc)(int);
सी शैली कॉलबैक फ़ंक्शन हो सकता है, लेकिन यह खराब डिज़ाइन का एक बहुत ही बेकार है।
एक अच्छी तरह से डिज़ाइन की गई सी स्टाइल कॉलबैक की तरह दिखता है void (*callbackFunc)(void*, int);
- इसमें एक void*
कोड की अनुमति है जो फ़ंक्शन से परे राज्य को बनाए रखने के लिए कॉलबैक करता है। ऐसा नहीं करने से कॉल करने वाले को विश्व स्तर पर राज्य को संग्रहीत करने के लिए मजबूर किया जाता है, जो कि अशुद्धता है।
std::function< int(int) >
int(*)(void*, int)
सबसे कार्यान्वयन में चालान की तुलना में थोड़ा अधिक महंगा है । हालाँकि कुछ इनलाइनर्स इनलाइन के लिए कठिन है। वहांstd::function
क्लोन कार्यान्वयन कि प्रतिद्वंद्वी समारोह सूचक invokation ओवरहेड्स कि पुस्तकालयों में अपना स्थान बना सकता है ( 'सबसे तेजी से संभव प्रतिनिधियों' आदि देखें)।
अब, कॉलबैक सिस्टम के क्लाइंट को अक्सर कॉलबैक बनाने और निकालने पर संसाधनों को सेट करने और उन्हें निपटाने की आवश्यकता होती है, और कॉलबैक के जीवनकाल के बारे में पता होना चाहिए। void(*callback)(void*, int)
यह प्रदान नहीं करता है।
कभी-कभी यह कोड संरचना (कॉलबैक सीमित जीवनकाल) या अन्य तंत्र (अपंजीकृत कॉलबैक और इस तरह) के माध्यम से उपलब्ध है।
std::function
सीमित आजीवन प्रबंधन के लिए एक साधन प्रदान करता है (जब यह भूल जाता है तो वस्तु की अंतिम प्रतिलिपि चली जाती है)।
सामान्य तौर पर, मैं std::function
तब तक उपयोग करूँगा जब तक कि प्रदर्शन संबंधी चिंताएँ प्रकट न हों। यदि उन्होंने ऐसा किया है, तो मैं पहली बार संरचनात्मक परिवर्तनों की तलाश करूँगा (प्रति पिक्सेल कॉलबैक के बजाय, आप लैम्बडा के एक स्कैन प्रोसेसर के निर्माण के बारे में जो आप मुझे पास करते हैं? जो कि फ़ंक्शन-कॉल ओवरहेड को तुच्छ स्तरों तक कम करने के लिए पर्याप्त होना चाहिए। )। फिर, अगर यह जारी रहता है, तो मैं delegate
सबसे तेज़ संभव प्रतिनिधियों को एक आधारित लिखूंगा , और देखूंगा कि प्रदर्शन की समस्या दूर हो जाती है या नहीं।
मैं अधिकांशतः केवल लीगेसी एपीआई के लिए फंक्शन पॉइंटर्स का उपयोग करूँगा, या विभिन्न कंपाइलर जनरेट कोड के बीच संचार के लिए C इंटरफेस बनाने के लिए। जब मैंने जंप टेबल, टाइप इरेज़र इत्यादि को लागू कर रहा हूँ, तो मैंने उन्हें आंतरिक कार्यान्वयन विवरण के रूप में भी उपयोग किया है: जब मैं दोनों का उत्पादन और उपभोग कर रहा हूं, और किसी भी क्लाइंट कोड का उपयोग करने के लिए इसे बाहरी रूप से उजागर नहीं कर रहा हूं, और फ़ंक्शन पॉइंटर्स को मेरी ज़रूरत है ।
ध्यान दें कि आप रैपर लिख सकते हैं std::function<int(int)>
जो int(void*,int)
स्टाइल कॉलबैक में बदल जाता है , यह मानते हुए कि उचित कॉलबैक लाइफटाइम मैनेजमेंट इंफ्रास्ट्रक्चर है। इसलिए किसी भी सी-स्टाइल कॉलबैक लाइफटाइम मैनेजमेंट सिस्टम के लिए एक स्मोक टेस्ट के रूप में, मैं यह सुनिश्चित std::function
करता हूं कि किसी कार्य को यथोचित रूप से लपेटना ।
void*
से आया? आप समारोह से परे राज्य को क्यों बनाए रखना चाहेंगे? एक फ़ंक्शन में सभी कोड होने चाहिए, सभी कार्यक्षमता, आप बस इसे वांछित तर्क पास करते हैं और कुछ को संशोधित और वापस करते हैं। अगर आपको किसी बाहरी राज्य की आवश्यकता है तो एक फंक्शनपार्ट या कॉलबैक उस सामान को क्यों ले जाएगा? मुझे लगता है कि कॉलबैक अनावश्यक रूप से जटिल है।
this
। क्या ऐसा इसलिए है क्योंकि किसी सदस्य को कॉल किए जाने वाले फ़ंक्शन के मामले को ध्यान में रखना है, इसलिए हमें this
ऑब्जेक्ट के पते को इंगित करने के लिए पॉइंटर की आवश्यकता है ? अगर मैं गलत हूं तो आप मुझे एक लिंक दे सकते हैं जहां मैं इस बारे में अधिक जानकारी पा सकता हूं, क्योंकि मैं इसके बारे में बहुत कुछ नहीं पा सकता हूं। अग्रिम में धन्यवाद।
void*
रनटाइम स्टेट के प्रसारण की अनुमति देता है। एक void*
और एक void*
तर्क के साथ एक फ़ंक्शन पॉइंटर एक ऑब्जेक्ट में एक सदस्य फ़ंक्शन कॉल का अनुकरण कर सकता है। क्षमा करें, मुझे ऐसे संसाधन का पता नहीं है जो "डिजाइनिंग कॉल कॉलबैक तंत्र 101" के माध्यम से चलता है।
this
। मेरा मतलब यही था। कोई बात नहीं, धन्यवाद।
std::function
मनमानी कॉल करने योग्य वस्तुओं को स्टोर करने के लिए उपयोग करें । यह उपयोगकर्ता को कॉलबैक के लिए जो भी संदर्भ आवश्यक है, प्रदान करने की अनुमति देता है; एक सादे समारोह सूचक नहीं करता है।
यदि आपको किसी कारण से सादे फ़ंक्शन पॉइंटर्स का उपयोग करने की आवश्यकता है (शायद इसलिए कि आप एक सी-संगत एपीआई चाहते हैं), तो आपको एक void * user_context
तर्क जोड़ना चाहिए ताकि इसके लिए कम से कम संभव (यद्यपि असुविधाजनक) राज्य का उपयोग करने के लिए इसे सीधे पास नहीं किया जा सके। समारोह।
बचने std::function
का एकमात्र कारण इस टेम्पलेट के लिए समर्थन की कमी वाले विरासत संकलकों का समर्थन है, जिसे C ++ 11 में पेश किया गया है।
यदि प्री-सी ++ 11 भाषा का समर्थन करना एक आवश्यकता नहीं है, std::function
तो कॉलबैक को लागू करने में आपके कॉलर्स को अधिक विकल्प का उपयोग करना पड़ता है, जिससे यह "सादे" फ़ंक्शन पॉइंटर्स की तुलना में बेहतर विकल्प बन जाता है। यह आपके API के उपयोगकर्ताओं को अधिक विकल्प प्रदान करता है, जबकि कॉलबैक करने वाले आपके कोड के लिए उनके कार्यान्वयन की बारीकियों को समाप्त कर देता है।