35

Boost Signals लाइब्रेरी में, वे() ऑपरेटर को अधिभारित कर रहे हैं।ऑपरेटर() को ओवरराइड क्यों करें?

क्या यह सी ++ में एक सम्मेलन है? कॉलबैक के लिए, आदि?

मैंने इसे सह-कार्यकर्ता के कोड में देखा है (जो एक बड़ा बूस्ट प्रशंसक होता है)। वहां सभी बूस्ट भलाई में से, इसने केवल मेरे लिए भ्रम पैदा कर दिया है।

इस अधिभार के कारण के रूप में कोई अंतर्दृष्टि?

+1

संबंधित http://stackoverflow.com/questions/356950/c-functors-and-their-uses? – Konrad

उत्तर

100

ऑपरेटर() को ओवरलोड करने पर प्राथमिक लक्ष्य में से एक एक मज़ेदार बनाना है। एक मज़ेदार एक समारोह की तरह कार्य करता है, लेकिन इसमें फायदे हैं कि यह राज्यपूर्ण है, जिसका अर्थ है कि यह कॉल के बीच अपने राज्य को प्रतिबिंबित करने वाले डेटा को रख सकता है।

struct Accumulator 
{ 
    int counter = 0; 
    int operator()(int i) { return counter += i; } 
} 
... 
Accumulator acc; 
cout << acc(10) << endl; //prints "10" 
cout << acc(20) << endl; //prints "30" 

functors भारी सामान्य प्रोग्रामिंग के साथ उपयोग किया जाता है:

यहाँ एक सरल functor उदाहरण है। कई एसटीएल एल्गोरिदम एक बहुत ही सामान्य तरीके से लिखे जाते हैं, ताकि आप अपने स्वयं के फ़ंक्शन/फ़ंक्शन को एल्गोरिदम में प्लग-इन कर सकें। उदाहरण के लिए, एल्गोरिदम std :: for_each आपको किसी श्रेणी के प्रत्येक तत्व पर एक ऑपरेशन लागू करने की अनुमति देता है। यह ऐसा ही कुछ लागू किया जा सकता:

template <typename InputIterator, typename Functor> 
void for_each(InputIterator first, InputIterator last, Functor f) 
{ 
    while (first != last) f(*first++); 
} 

आप देखते हैं कि इस एल्गोरिथ्म बहुत सामान्य है, क्योंकि यह एक समारोह से parametrized है। ऑपरेटर() का उपयोग करके, यह फ़ंक्शन आपको या तो एक फ़ैक्टर या फ़ंक्शन पॉइंटर का उपयोग करने देता है। यहाँ दोनों संभावनाओं दिखा एक उदाहरण है:

void print(int i) { std::cout << i << std::endl; } 
...  
std::vector<int> vec; 
// Fill vec 

// Using a functor 
Accumulator acc; 
std::for_each(vec.begin(), vec.end(), acc); 
// acc.counter contains the sum of all elements of the vector 

// Using a function pointer 
std::for_each(vec.begin(), vec.end(), print); // prints all elements 

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

+0

मुझे लगता है कि इस उत्तर का एक बड़ा हिस्सा एसटीएल for_each का वाक्यविन्यास है। ऑपरेटर() को मज़ेदार के ऑपरेशन हिस्से के रूप में उपयोग करके यह एसटीएल के साथ अच्छी तरह से काम करेगा। – JeffV

+0

ऐसा लगता है कि यदि एसटीएल को ऑपरेटर()() {...} के बजाय() {...} के बजाय लागू किया जाएगा (...} का उपयोग इसके बजाय किया जाएगा। – JeffV

+3

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

1

एक अन्य सहकर्मी ने बताया कि यह फंक्चर ऑब्जेक्ट्स को कार्यों के रूप में छिपाने का एक तरीका हो सकता है। उदाहरण के लिए, इस:

my_functor(); 

वास्तव में है:

my_functor.operator()(); 

तो यह है कि इस का क्या मतलब है:

my_functor(int n, float f){ ... }; 

रूप में अच्छी तरह यह ओवरलोड के लिए इस्तेमाल किया जा सकता है?

my_functor.operator()(int n, float f){ ... }; 
+0

आपकी आखिरी पंक्ति बिल्कुल एक ऑपरेटर अधिभार नहीं है। यह होना चाहिए: "। ऑपरेटर() (int n, float f)" जो पहली बार आप इसे देखते हुए बहुत भ्रमित लग रहा है। आप इस "फंक्शन कॉल ऑपरेटर" को अन्य कार्यों की तरह अधिभारित कर सकते हैं, लेकिन आप इसे निर्दिष्ट गैर-ऑपरेटर अधिभार के साथ अधिभारित नहीं कर सकते हैं। – altruic

+0

@altruic, अच्छा बिंदु। बस इसे ठीक कर दिया। – JeffV

+0

आपकी दूसरी पंक्ति गलत है, यह वास्तव में "my_functor.operator()();" है। my_functor.operator() विधि संदर्भ है, जबकि() का दूसरा सेट आमंत्रण को दर्शाता है। – eduffy

2

आप C++ faq's Matrix example पर भी देख सकते हैं। ऐसा करने के लिए अच्छे उपयोग हैं लेकिन यह निश्चित रूप से उस पर निर्भर करता है जो आप पूरा करने की कोशिश कर रहे हैं।

4

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

जैसा कि अन्य पोस्टर ने कहा है: मज़दूरों के पास सादे कार्यों पर लाभ होता है जिसमें वे राज्य हो सकते हैं। इस राज्य का उपयोग एक ही पुनरावृत्ति पर किया जा सकता है (उदाहरण के लिए कंटेनर में सभी तत्वों के योग की गणना करने के लिए) या एकाधिक पुनरावृत्तियों पर (उदाहरण के लिए विशेष मानदंडों को पूरा करने वाले कई कंटेनरों में सभी तत्वों को ढूंढने के लिए)।

18

यह कक्षा को एक समारोह की तरह कार्य करने की अनुमति देता है। मैंने इसे लॉगिंग क्लास में इस्तेमाल किया है जहां कॉल एक समारोह होना चाहिए लेकिन मैं कक्षा के अतिरिक्त लाभ चाहता था।

तो कुछ इस तरह:

logger.log("Log this message"); 

इस में बदल जाता है:

logger("Log this message"); 
3

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

2

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

5

कई ने जवाब दिया है कि यह एक बड़ा कारण बताए बिना एक मज़ेदार बनाता है कि एक मज़ेदार एक पुराने पुराने कार्य से बेहतर क्यों है।

उत्तर यह है कि एक मजेदार के पास राज्य हो सकता है। एक संक्षिप्त कार्य पर विचार करें - इसे एक रनिंग कुल रखने की जरूरत है।

class Sum 
{ 
public: 
    Sum() : m_total(0) 
    { 
    } 
    void operator()(int value) 
    { 
     m_total += value; 
    } 
    int m_total; 
}; 
+0

यह स्पष्ट नहीं करता है कि इस तथ्य को छिपाने की आवश्यकता क्यों है कि यह एक वस्तु है और इसे एक समारोह के रूप में मास्कराइड किया गया है। – JeffV

+5

जेफ वी: सुविधा। इसका मतलब है कि कॉल करने के लिए एक ही वाक्यविन्यास का उपयोग किया जा सकता है, भले ही हम एक मजेदार या फ़ंक्शन पॉइंटर कह रहे हों। यदि आप std :: for_each देखते हैं, उदाहरण के लिए, यह या तो फ़ैक्टर या फ़ंक्शन पॉइंटर्स के साथ काम करता है, क्योंकि दोनों मामलों में, कॉल के लिए वाक्यविन्यास समान है। – jalf

2

सी ++ में functors बनाने के लिए ऑपरेटर() का उपयोग functional programming प्रतिमान से संबंधित है जो आमतौर पर एक समान अवधारणा का उपयोग करते हैं: closures

2

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

T t; 
t.write("Hello world"); 

सामान नहीं कर सका क्योंकि टी आवश्यकता होती एक सदस्य समारोह बुलाया लिखने जो स्थिरांक चार के लिए कुछ भी परोक्ष castable स्वीकार करता है कि है * (या बल्कि कॉन्स चार [])। इस उदाहरण में रिपोर्टर क्लास में ऐसा नहीं है, इसलिए टी (टेम्पलेट पैरामीटर) रिपोर्टर होने के कारण संकलन करने में विफल रहेगा।

हालांकि, जहां तक ​​मैं इस विभिन्न प्रकार

T t; 
t("Hello world"); 

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

सिंथेटिक चीनी व्यवहार के अलावा मैं वास्तव में ऐसे कार्यों को करने के लिए ऑपरेटर ओवरलोडिंग की ताकत नहीं देखता हूं।

मुझे यकीन है कि ऐसे जानकार लोग हैं जिनके पास मेरे पास बेहतर कारण हैं लेकिन मैंने सोचा कि मैं आपके बाकी हिस्सों को साझा करने के लिए अपनी राय रखूंगा।

+2

कॉल को त्रिकोणीय रूप से इनलाइन करने की अनुमति देता है ऑपरेटर() का उपयोग करने का लाभ यह है कि आपका टेम्पलेट पैरामीटर समान रूप से फ़ंक्शन पॉइंटर या फ़ैक्टर हो सकता है। –

1

अन्य पोस्टों ने यह वर्णन किया है कि ऑपरेटर() कैसे काम करता है और यह उपयोगी क्यों हो सकता है।

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

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