क्या एक C ++ मेथड को C फ़ंक्शन को पॉइंटर तर्क के साथ एक स्वीकार्य पैटर्न में परिवर्तित करना है?


16

मैं ESP-32 पर C ++ का उपयोग करता हूं। टाइमर रजिस्टर करते समय मुझे यह करना होगा:

timer_args.callback = reinterpret_cast<esp_timer_cb_t>(&SoundMixer::soundCallback);
timer_args.arg = this;

यहाँ टाइमर कॉल करता है soundCallback

और एक ही बात जब एक काम रजिस्टर:

xTaskCreate(reinterpret_cast<TaskFunction_t>(&SoundProviderTask::taskProviderCode), "SProvTask", stackSize, this, 10, &taskHandle);

इसलिए विधि को एक अलग कार्य में शुरू किया गया है।

जीसीसी हमेशा मुझे इन रूपांतरणों के बारे में चेतावनी देता है, लेकिन यह योजनाबद्ध तरीके से काम करता है।

क्या यह उत्पादन कोड में स्वीकार्य है? क्या ऐसा करने के लिए इससे अच्छा तरीका है?

जवाबों:


47

A reinterpret_castहमेशा गड़बड़ है जब तक आपको पता नहीं है कि आप क्या कर रहे हैं। यहां, आपका कोड केवल C ++ विधियों के लिए GCC के कॉलिंग कन्वेंशन के कारण काम करने के लिए होता है, लेकिन यह अपरिभाषित व्यवहार की तरह महक देता है। विशेष रूप से आपको यह नहीं मानना ​​चाहिए कि सदस्य फ़ंक्शन किसी भी तरह से सामान्य फ़ंक्शन पॉइंटर्स के साथ संगत हैं।

सामान्य दृष्टिकोण इसके बजाय उचित हस्ताक्षर के साथ सी-संगत फ़ंक्शन को परिभाषित करना होगा, जिसे आंतरिक रूप से C ++ विधि कहा जाता है। उदाहरण के लिए:

extern "C" static void my_timer_callback(void* arg) {
  static_cast<SoundMixer*>(arg)->soundCallback();
}

यह कास्ट ठीक है क्योंकि हम एक void*प्रकार से इंगित-टू-ऑब्जेक्ट के प्रकार से वापस कास्टिंग कर रहे हैं ।

विवरण:

  • extern "C"इस फ़ंक्शन की भाषा लिंकेज निर्दिष्ट करता है। भाषा लिंकेज नाम की मैनिंग और फ़ंक्शन के कॉलिंग सम्मेलन को प्रभावित करता है । सदस्य कार्यों में सी भाषा लिंकेज नहीं हो सकता है। भाषा लिंकेज आंतरिक / बाहरी लिंकेज के लिए काफी हद तक रूढ़िवादी है।

  • कॉलबैक के लिए फ़ंक्शन "निजी" हो सकता है, अर्थात आंतरिक लिंकेज हो सकता है। C कोड कभी भी नाम से कॉलबैक को संदर्भित नहीं करता है। उपरोक्त कोड स्निपेट staticकीवर्ड के माध्यम से आंतरिक जुड़ाव निर्दिष्ट करता है (स्थैतिक विधि नहीं!)। वैकल्पिक रूप से, फ़ंक्शन को एक अनाम नामस्थान में रखा जा सकता था।

    मैं ( extern "C"और staticआंतरिक संबंध) के बीच की बातचीत के बारे में पूरी तरह सुनिश्चित नहीं हूं । ईजी का [dcl.link]कहना है कि "सभी फ़ंक्शन प्रकार, बाहरी लिंकेज के साथ फ़ंक्शन नाम और बाहरी लिंकेज वाले चर नामों में एक भाषा लिंकेज होता है।" मैं इसकी व्याख्या करता हूं ताकि सी भाषा के प्रकार में my_timer_callbackलिंकेज हो, लेकिन इसका फ़ंक्शन नाम नहीं है।

  • A static_castयहां उपयुक्त है क्योंकि हम वास्तविक प्रकार को जानते हैं argलेकिन इसे टाइप सिस्टम के भीतर व्यक्त नहीं कर सकते। इसके विपरीत एक reinterpret_castउपयुक्त है जब हम एक बिट पैटर्न को फिर से व्याख्या करना चाहते हैं, जैसे एक संख्यात्मक प्रकार के लिए एक सूचक।

  • फ़ंक्शंस साधारण ऑब्जेक्ट नहीं हैं, और सदस्य फ़ंक्शन भी कम हैं। आप फ़ंक्शन पॉइंटर प्रकारों के बीच पुन: व्याख्या-कास्ट कर सकते हैं जब तक कि फ़ंक्शन केवल अपने वास्तविक प्रकार (और सदस्य फ़ंक्शन पॉइंटर्स के अनुरूप) के माध्यम से लगाया जाता है। चाहे आप फ़ंक्शन पॉइंटर्स को अन्य प्रकारों में डाल सकते हैं (जैसे ऑब्जेक्ट पॉइंटर्स या शून्य पॉइंटर्स) कार्यान्वयन-परिभाषित ( पृष्ठभूमि )। POSIX में फंक्शन पॉइंटर्स के बीच कास्ट किया जाता है और void*इसे अनुमति दी जाती है ताकि यह dlsym()काम कर सके। समारोह में शामिल अन्य सदस्य (सदस्य) सूचक अपरिभाषित होते हैं। विशेष रूप से, सदस्य फ़ंक्शन और फ़ंक्शन पॉइंटर्स के बीच कास्ट संभव नहीं हैं।


1
std::bindपहली विधि तर्क के रूप में ऑब्जेक्ट पॉइंटर को भी नहीं मानता है?
वैल का कहना है कि मोनिका

5
@val हाँ, लेकिन इसका मतलब यह नहीं है कि सदस्य फ़ंक्शन सामान्य फ़ंक्शन के साथ संगत हैं, केवल उस बाइंड () INVOKE एल्गोरिथ्म का उपयोग करता है जो सदस्य फ़ंक्शन को सामान्य फ़ंक्शन ऑब्जेक्ट incl से एक अलग मामले के रूप में संभालता है। समारोह संकेत। क्योंकि std :: bind () एक ऐसा फन्नेकार बनाता है जो सी के साथ इंटरफेस करने के लिए उपयुक्त नहीं है
amon

1
एक और सवाल: मुझे extern "C"यहाँ की आवश्यकता क्यों है ? क्या इस मामले में सी लिंकेज महत्वपूर्ण है?
वैल का कहना है कि मोनिका

5
@val यदि आप C से उस फ़ंक्शन को कॉल करने में सक्षम होना चाहते हैं, तो उसे C कॉलिंग कन्वेंशन का उपयोग करना चाहिए। यह उस फ़ंक्शन को C भाषा लिंकेज के साथ, या संकलक-विशिष्ट एक्सटेंशन (जैसे __attribute__((cdecl)), लेकिन कृपया ऐसा न करें) घोषित करके किया जा सकता है । C ++ फ़ंक्शन को C- संगत कॉलिंग कन्वेंशन करने की गारंटी नहीं है, हालांकि (जीसीसी में यह आमतौर पर ठीक काम करता है)।
अमन

4
@val extern "C"औपचारिक रूप से आवश्यक क्यों है, इस बारे में जानकारी के लिए , [dcl.link]"दो कार्य प्रकार di language erent भाषा लिंकेज के साथ अलग-अलग प्रकार के होते हैं, भले ही वे समान हों।" और [expr.call]"एक फ़ंक्शन को एक अभिव्यक्ति के माध्यम से कॉल करना जिसका फ़ंक्शन प्रकार di ent है जिसे फ़ंक्शन फ़ंक्शन के फ़ंक्शन प्रकार से बुलाया गया है" unde ned व्यवहार में परिणाम "
बेन Voigt

-1

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


6
क्या ऐसा नहीं है कि क्या जवाब दिया?
द्रोण्ज

1
@ डॉर्नोज़ एक दूसरे पढ़ने के बाद, हाँ, यह ज्यादातर यही है। जैसे ही मैंने पढ़ा staticमैंने इसे एक विधि के रूप में देखा और किसी कारण से यह महसूस नहीं किया कि यह thisसूचक को पहले तर्क के रूप में पारित नहीं करता है (और std::bindप्रबलित के उपयोग पर निम्नलिखित बहस )। लेकिन हाँ, आप बिलकुल सही कह रहे हैं! (दोहरे उत्तर के लिए क्षमा करें!)
जीसस अलोंसो अबाद

3
हाँ, staticकम से कम तीन अलग-अलग अर्थ हैं। और अगर आप सावधान नहीं हैं तो आप उन्हें मिलाएंगे। मैं कहूंगा, यह वास्तव में के विभिन्न उपयोगों के बीच के अंतर को समझने में मददगार है static, क्योंकि प्रत्येक अपने आप में एक महान उपकरण है।
cmaster - मोनिका
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.