क्या मुझे सी ++ में फ़ंक्शन :: फ़ंक्शन या फ़ंक्शन पॉइंटर का उपयोग करना चाहिए?


142

C ++ में कॉलबैक फ़ंक्शन लागू करते समय, क्या मुझे अभी भी C-style फ़ंक्शन पॉइंटर का उपयोग करना चाहिए:

void (*callbackFunc)(int);

या मुझे std :: function का उपयोग करना चाहिए:

std::function< void(int) > callbackFunc;

9
यदि कॉलबैक फ़ंक्शन को संकलन समय पर जाना जाता है, तो इसके बजाय एक टेम्पलेट पर विचार करें।
बौम Augen mit

4
जब लागू करने कॉलबैक फ़ंक्शन आप जो कुछ भी फोन करने वाले की आवश्यकता है क्या करना चाहिए। यदि आपका प्रश्न वास्तव में कॉलबैक इंटरफ़ेस डिज़ाइन करने के बारे में है, तो इसका उत्तर देने के लिए यहाँ पर्याप्त जानकारी नहीं है। आप अपने कॉलबैक के प्राप्तकर्ता को क्या करना चाहते हैं? प्राप्तकर्ता को पास करने के लिए आपको क्या जानकारी चाहिए? कॉल के परिणामस्वरूप प्राप्तकर्ता को क्या जानकारी वापस देनी चाहिए?
पीट बेकर

जवाबों:


171

संक्षेप में,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


9
फंक्शन पॉइंटर्स में टेम्प्लेट मापदंडों की तुलना में ओवरहेड कॉल होता है। टेम्प्लेट पैरामीटर इनलाइनिंग को आसान बनाते हैं, भले ही आप ऊपर के स्तर से नीचे पारित किए गए हों, क्योंकि निष्पादित कोड को पैरामीटर के प्रकार द्वारा वर्णित किया गया है न कि मान। और टेम्पलेट फ़ंक्शन ऑब्जेक्ट्स को टेम्पलेट रिटर्न प्रकारों में संग्रहीत किया जा रहा है, एक सामान्य और उपयोगी पैटर्न है (एक अच्छे कॉपी कंस्ट्रक्टर के साथ, आप कुशल टेम्प्लेट फ़ंक्शन को अदृश्य बना सकते हैं जिसे std::functionटाइप-मिटाने के लिए परिवर्तित किया जा सकता है यदि आपको इसे तुरंत स्टोर करने की आवश्यकता है संदर्भ कहा जाता है)।
यक्क - एडम नेवेरमोंट

1
@tohecz मैं अब उल्लेख करता हूं कि इसे C ++ 11 की आवश्यकता है या नहीं।
लीमेस

1
@Yakk ओह, इस बारे में भूल गया! इसे जोड़ा, धन्यवाद।
leemes

1
@MingDuck बेशक यह कार्यान्वयन पर निर्भर करता है। लेकिन अगर मुझे सही ढंग से याद है, तो किस प्रकार के क्षरण के कारण एक और अप्रत्यक्ष रूप से काम हो रहा है? लेकिन अब मैं इसके बारे में फिर से लगता है कि, मुझे लगता है कि इस मामले में अगर आप असाइन समारोह संकेत दिए गए या कब्जा-कम यह करने के लिए lambdas ... (एक ठेठ अनुकूलन के रूप में) नहीं है
leemes

1
@ एलिमेस: राइट, फंक्शन पॉइंटर्स या कैप्चरलेस लैम्ब्डा के लिए, यह सी- फंक-पीटीआर के समान ओवरहेड होना चाहिए । जो अभी भी एक पाइपलाइन स्टाल है + तुच्छ इनलाइन नहीं है।
मूविंग डक

25

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करता हूं कि किसी कार्य को यथोचित रूप से लपेटना ।


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

@ nik-lz मैं अनिश्चित हूं कि मैं आपको एक टिप्पणी में सी में कॉलबैक के उपयोग और इतिहास को कैसे सिखाऊंगा। या कार्यात्मक प्रोग्रामिंग के विपरीत प्रक्रियात्मक का दर्शन। तो, आप अधूरा छोड़ देंगे।
यक्क - एडम नेवरामॉन्ट

मैं भूल गया this। क्या ऐसा इसलिए है क्योंकि किसी सदस्य को कॉल किए जाने वाले फ़ंक्शन के मामले को ध्यान में रखना है, इसलिए हमें thisऑब्जेक्ट के पते को इंगित करने के लिए पॉइंटर की आवश्यकता है ? अगर मैं गलत हूं तो आप मुझे एक लिंक दे सकते हैं जहां मैं इस बारे में अधिक जानकारी पा सकता हूं, क्योंकि मैं इसके बारे में बहुत कुछ नहीं पा सकता हूं। अग्रिम में धन्यवाद।
निकोस

@ Nik-Lz सदस्य फ़ंक्शन फ़ंक्शंस नहीं हैं। क्रियाओं की कोई (रनटाइम) स्थिति नहीं है। कॉलबैक void*रनटाइम स्टेट के प्रसारण की अनुमति देता है। एक void*और एक void*तर्क के साथ एक फ़ंक्शन पॉइंटर एक ऑब्जेक्ट में एक सदस्य फ़ंक्शन कॉल का अनुकरण कर सकता है। क्षमा करें, मुझे ऐसे संसाधन का पता नहीं है जो "डिजाइनिंग कॉल कॉलबैक तंत्र 101" के माध्यम से चलता है।
यक्क - एडम नेवरामोंट

हाँ, यही मैं बात कर रहा था। रनटाइम स्टेट मूल रूप से ऑब्जेक्ट का पता कहा जा रहा है (क्योंकि यह रन के बीच बदलता है)। यह अभी भी है this। मेरा मतलब यही था। कोई बात नहीं, धन्यवाद।
निकोस

17

std::functionमनमानी कॉल करने योग्य वस्तुओं को स्टोर करने के लिए उपयोग करें । यह उपयोगकर्ता को कॉलबैक के लिए जो भी संदर्भ आवश्यक है, प्रदान करने की अनुमति देता है; एक सादे समारोह सूचक नहीं करता है।

यदि आपको किसी कारण से सादे फ़ंक्शन पॉइंटर्स का उपयोग करने की आवश्यकता है (शायद इसलिए कि आप एक सी-संगत एपीआई चाहते हैं), तो आपको एक void * user_contextतर्क जोड़ना चाहिए ताकि इसके लिए कम से कम संभव (यद्यपि असुविधाजनक) राज्य का उपयोग करने के लिए इसे सीधे पास नहीं किया जा सके। समारोह।


पी के प्रकार यहाँ क्या है? क्या यह एक std :: फ़ंक्शन प्रकार होगा? शून्य f () {}; ऑटो पी = एफ; पी ();
श्री

14

बचने std::functionका एकमात्र कारण इस टेम्पलेट के लिए समर्थन की कमी वाले विरासत संकलकों का समर्थन है, जिसे C ++ 11 में पेश किया गया है।

यदि प्री-सी ++ 11 भाषा का समर्थन करना एक आवश्यकता नहीं है, std::functionतो कॉलबैक को लागू करने में आपके कॉलर्स को अधिक विकल्प का उपयोग करना पड़ता है, जिससे यह "सादे" फ़ंक्शन पॉइंटर्स की तुलना में बेहतर विकल्प बन जाता है। यह आपके API के उपयोगकर्ताओं को अधिक विकल्प प्रदान करता है, जबकि कॉलबैक करने वाले आपके कोड के लिए उनके कार्यान्वयन की बारीकियों को समाप्त कर देता है।


1

std::function कुछ मामलों में वीएमटी को कोड में ला सकता है, जो प्रदर्शन पर कुछ प्रभाव डालता है।


3
क्या आप समझा सकते हैं कि यह VMT टोपी है?
गुप्ता
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.