लैम्ब्डा फ़ंक्शन और C ++ में सुविधाजनक कॉलबैक तंत्र का कार्यान्वयन

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

समस्या का बयान


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

विधि 1 - क्लासिक "सी" पर


समस्या को हल करने में "माथे में" आप कुछ इस तरह प्राप्त कर सकते हैं:

//   static void MyFunction(char *s){ puts(s); } int main(){ //,     void (*MyCallback)(char *argument); //     MyCallback=MyFunction; //    MyCallback("123"); return 0; } 

तंत्र बहुत सरल है और अक्सर उपयोग किया जाता है। लेकिन बड़ी संख्या में कॉलबैक के साथ, उनकी घोषणा बहुत सुविधाजनक नहीं होती है।

C + + में लैम्ब्डा कार्य करता है


उन लोगों के लिए जिन्होंने C ++ 11 (या C ++ 0x) के बारे में नहीं सुना है या अभी तक इसे नहीं छुआ है, मैं इस मानक से कुछ नवाचारों के बारे में बात करूंगा। C ++ 11 में, ऑटो कीवर्ड दिखाई दिया, जिसे प्रारंभ के साथ एक चर घोषित करते समय एक प्रकार के बजाय सेट किया जा सकता है। इस मामले में, चर का प्रकार "=" के बाद दिखाए गए प्रकार के समान होगा। उदाहरण के लिए:
  auto a=1; //    int a=1; auto b=""; //    const char* b=1; auto c=1.2; //    double c=1; auto d; // !     d 


लेकिन सबसे दिलचस्प है लंबोदर फ़ंक्शन। सिद्धांत रूप में, ये सामान्य कार्य हैं, लेकिन जिन्हें सीधे अभिव्यक्ति में घोषित किया जा सकता है:
 [](int a,int b) -> bool //    ,  bool { return a>b; } 


लैम्ब्डा फ़ंक्शन का सिंटैक्स निम्नानुसार है:

 [ ]()-> {   } 


"-> वापसी प्रकार" टुकड़ा गायब हो सकता है। तब इसका अर्थ है "-> शून्य"। उपयोग का दूसरा उदाहरण:
 int main(int argc,char *argv[]){ //,  abs(int) auto f1=[](int a)->int{ return (a>0)?(a):(-a); }; //,     0.0  1.0 auto f2=[]()->float{ return float(rand())/RAND_MAX; }; //,   enter auto f3=[](){ puts("Press enter to continue..."); getchar(); }; printf("%d %d\n",f1(5),f1(-10)); printf("%f %f\n",f2(),f2()); f3(); return 0; } 


यह कार्यक्रम आउटपुट देगा:

 5 10 0.563585 0.001251 Press enter to continue... 


इस उदाहरण में, प्रकार के ऑटो के तीन चर (f1, f2 और f3) घोषित किए गए और आरंभिक थे, इसलिए उनमें से प्रकार प्रकार से दाईं ओर - लंबोदर प्रकार का कार्य करता है।
एक लैम्ब्डा फ़ंक्शन, अपने आप में, एक फ़ंक्शन का सूचक नहीं है (हालांकि कुछ मामलों में इसे इसे लाया जा सकता है)। संकलक पते पर फ़ंक्शन को कॉल नहीं करता है, लेकिन इसके प्रकार के अनुसार - यही कारण है कि प्रत्येक फ़ंक्शन लंबो का अपना प्रकार है, उदाहरण के लिए, "<lambda_a48784a181f11f18d942adab3b2ffca>"। इस प्रकार को निर्दिष्ट नहीं किया जा सकता है, इसलिए इसका उपयोग केवल ऑटो या टेम्प्लेट के संयोजन में किया जा सकता है (वहां भी प्रकार को स्वचालित रूप से निर्धारित किया जा सकता है)।
मानक भी कैद किए गए चर की अनुपस्थिति में लंबर प्रकार से फ़ंक्शन पॉइंटर प्रकार तक रूपांतरण की अनुमति देता है:

 void(*func)(int arg); func= [](int arg){ ... }; //  ,   


कैप्चर किए गए वैरिएबल वे वैरिएबल हैं जो "लैम्ब्डा फंक्शन" के अंदर मिलते हैं जब इसे निर्दिष्ट किया जाता है:
 int main(int argc,char *argv[]){ auto f=[argc,&argv](char *s){ puts(s); for(int c=0;c<argc;c++){ puts(argv[c]); } }; f("123"); return 0; } 


इन मापदंडों को वास्तव में चर (मूल्य द्वारा कॉपी) में संग्रहीत किया जाता है।
यदि आप नाम के सामने & संकेत निर्दिष्ट करते हैं, तो पैरामीटर को संदर्भ द्वारा पारित किया जाएगा, मूल्य द्वारा नहीं।
फ़ंक्शन का पता स्वयं अभी भी कहीं संग्रहीत नहीं है।

विधि 2 - C ++ में कार्यान्वयन


स्थैतिक कार्य को लंबोदर के साथ बदलने से हमारा उदाहरण सरल हो सकता है:

 int main(){ void (*MyCallback)(char *argument); //      ! MyCallback=[](char *s){ puts(s); }; MyCallback("123"); return 0; } 


इसलिए थोड़ा "प्लसस" जोड़ना जीवन को सरल बना सकता है, मुख्य बात यह ज़्यादा नहीं है, जिसे अब हम करने की कोशिश कर रहे हैं। इस उदाहरण में, ऐसा निर्माण तब तक काम करेगा जब तक हम लंबोदा फ़ंक्शन में चर को "कैप्चर" नहीं करना चाहते। तब कंपाइलर लैम्बडा को पॉइंटर में बदलने में सक्षम नहीं होगा। यहां, C ++ का उपयोग करके आप ऐसा कर सकते हैं:

 class Callback{ private: // ,       class FuncClass{ public: //   virtual void Call(char*)=0; }; //     FuncClass *function; public: Callback(){ function=0; } ~Callback(){ if(function) delete function; } template<class T> void operator=(T func){ if(function) delete function; //     Call,  func class NewFuncClass:public FuncClass{ public: T func; NewFuncClass(T f):func(f){ } void Call(char* d){ func(d); } }; //       function=new NewFuncClass(func); } void operator()(char* d){ if(function) function->Call(d); } }; int main(){ Callback MyCallback; MyCallback=[](char *s){ puts(s); }; MyCallback("123"); return 0; } 


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

आइए इसे ठीक करने की कोशिश करें और बिना सुविधा खोए जितना संभव हो उतना कार्यान्वयन को सरल और सरल बनाएं।

विधि 3 - कुछ मध्य


कार्यान्वयन:
 class Callback{ private: void (*function)(char*,void*); void *parameters[4]; public: Callback(){ function=[](char*,void*){ }; } template<class T> void operator=(T func){ //    ,  sizeof(T) <= sizeof(parameters) //    ,   compile-time , .. //      sizeof(int[ sizeof(parameters)-sizeof(T) ]); //    ,     func function=[](char* arg,void *param){ (*(T*)param)(arg); }; //     func  parameters memcpy(parameters,&func,sizeof(T)); } void operator()(char* d){ //     function,    parameters function(d,parameters); } }; int main(){ Callback MyCallback; MyCallback=[](char *s){ puts(s); }; MyCallback("123"); return 0; } 


सबसे पहले: हमने वर्चुअल फ़ंक्शंस और मेमोरी एलोकेशन से जुड़ा एक बड़ा हिस्सा निकाला। कई बाइट्स की कॉपी गति से बचत होती है।

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

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

परिणाम


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

Source: https://habr.com/ru/post/In159003/


All Articles