c++ - ऑपरेटर ओवरलोडिंग के लिए बुनियादी नियम और मुहावरे क्या हैं?




operators operator-overloading (5)

नोट: उत्तर एक विशिष्ट क्रम में दिए गए थे, लेकिन चूंकि कई उपयोगकर्ता वोट दिए गए समय के मुकाबले वोटों के अनुसार उत्तर देते हैं, यहां दिए गए उत्तरों में उत्तर की एक अनुक्रमणिका है जिसमें वे अधिकतर समझ में आते हैं:

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

https://code.i-harness.com


अधिभार के लिए आम ऑपरेटरों

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

असाइनमेंट ऑपरेटर

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

X& X::operator=(X rhs)
{
  swap(rhs);
  return *this;
}

बिट्सफ़्ट ऑपरेटर (स्ट्रीम I / O के लिए उपयोग किया जाता है)

बिट्सफ़्ट ऑपरेटर << और >> हालांकि, बिट-मैनिपुलेशन फ़ंक्शंस के लिए हार्डवेयर इंटरफेसिंग में अभी भी उपयोग किया जाता है, वे सी से प्राप्त होते हैं, अधिकतर अनुप्रयोगों में ओवरलोडेड स्ट्रीम इनपुट और आउटपुट ऑपरेटर के रूप में अधिक प्रचलित हो जाते हैं। बिट-मैनिपुलेशन ऑपरेटर के रूप में ओवरलोडिंग के लिए, बाइनरी अंकगणितीय ऑपरेटरों पर नीचे दिया गया अनुभाग देखें। Iostreams के साथ आपकी ऑब्जेक्ट का उपयोग होने पर अपने स्वयं के कस्टम प्रारूप को लागू करने और तर्क को पार्स करने के लिए, जारी रखें।

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

std::ostream& operator<<(std::ostream& os, const T& obj)
{
  // write obj to stream

  return os;
}

std::istream& operator>>(std::istream& is, T& obj)
{
  // read obj from stream

  if( /* no valid object of T found in stream */ )
    is.setstate(std::ios::failbit);

  return is;
}

operator>> कार्यान्वित operator>> स्ट्रीम की स्थिति को मैन्युअल रूप से सेट करना केवल तभी जरूरी होता है जब पठन स्वयं सफल हो जाए, लेकिन नतीजा यह नहीं होगा कि अपेक्षित क्या होगा।

फंक्शन कॉल ऑपरेटर

फ़ंक्शन कॉल ऑपरेटर, फ़ंक्शन ऑब्जेक्ट्स बनाने के लिए उपयोग किया जाता है, जिसे फ़ैक्टर के रूप में भी जाना जाता है, को सदस्य फ़ंक्शन के रूप में परिभाषित किया जाना चाहिए, इसलिए यह हमेशा सदस्य कार्यों के this तर्क को निहित करता है। इसके अलावा शून्य सहित कई अतिरिक्त तर्क लेने के लिए इसे ओवरलोड किया जा सकता है।

वाक्यविन्यास का एक उदाहरण यहां दिया गया है:

class foo {
public:
    // Overloaded call operator
    int operator()(const std::string& y) {
        // ...
    }
};

उपयोग:

foo f;
int a = f("hello");

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

तुलना ऑपरेटर

बाइनरी इंफिक्स तुलना ऑपरेटरों को अंगूठे के नियमों के अनुसार, गैर-सदस्य कार्य 1 के रूप में लागू किया जाना चाहिए। यूनरी उपसर्ग निषेध ! (एक ही नियम के अनुसार) एक सदस्य समारोह के रूप में लागू किया जाना चाहिए। (लेकिन इसे अधिभारित करना आमतौर पर एक अच्छा विचार नहीं है।)

मानक लाइब्रेरी के एल्गोरिदम (जैसे std::sort() ) और प्रकार (जैसे std::map ) हमेशा operator< उपस्थित होने की उम्मीद करेंगे। हालांकि, आपके प्रकार के उपयोगकर्ता अन्य सभी ऑपरेटरों को भी उपस्थित होने की उम्मीद करेंगे , इसलिए यदि आप operator< को परिभाषित करते हैं, तो ऑपरेटर ओवरलोडिंग के तीसरे मौलिक नियम का पालन करना सुनिश्चित करें और अन्य सभी बूलियन तुलना ऑपरेटर को भी परिभाषित करें। उन्हें लागू करने के लिए वैचारिक तरीका यह है:

inline bool operator==(const X& lhs, const X& rhs){ /* do actual comparison */ }
inline bool operator!=(const X& lhs, const X& rhs){return !operator==(lhs,rhs);}
inline bool operator< (const X& lhs, const X& rhs){ /* do actual comparison */ }
inline bool operator> (const X& lhs, const X& rhs){return  operator< (rhs,lhs);}
inline bool operator<=(const X& lhs, const X& rhs){return !operator> (lhs,rhs);}
inline bool operator>=(const X& lhs, const X& rhs){return !operator< (lhs,rhs);}

यहां ध्यान देने योग्य महत्वपूर्ण बात यह है कि इन ऑपरेटरों में से केवल दो ही वास्तव में कुछ भी करते हैं, अन्य लोग वास्तविक कार्य करने के लिए इन दोनों में से किसी एक को अपने तर्कों को अग्रेषित कर रहे हैं।

शेष बाइनरी बूलियन ऑपरेटर ( || , && ) को अधिभारित करने के लिए वाक्यविन्यास तुलना ऑपरेटर के नियमों का पालन करता है। हालांकि, यह बहुत ही असंभव है कि आपको इन 2 के लिए उचित उपयोग केस मिलेगा।

1 अंगूठे के सभी नियमों के साथ, कभी-कभी इसे तोड़ने के कारण भी हो सकते हैं। यदि ऐसा है, तो यह न भूलें कि द्विआधारी तुलना ऑपरेटर के बाएं हाथ के संचालन, जो सदस्य कार्यों के लिए *this , को भी const होना चाहिए। तो एक सदस्य समारोह के रूप में लागू एक तुलना ऑपरेटर को यह हस्ताक्षर होना होगा:

bool operator<(const X& rhs) const { /* do actual comparison with *this */ }

(अंत में const पर ध्यान दें।)

2 यह ध्यान दिया जाना चाहिए कि निर्मित संस्करण और && शॉर्टकट अर्थशास्त्र का उपयोग करें। जबकि उपयोगकर्ता परिभाषित लोगों (क्योंकि वे विधि कॉल के लिए वाक्य रचनात्मक चीनी हैं) शॉर्टकट अर्थशास्त्र का उपयोग नहीं करते हैं। उपयोगकर्ता इन ऑपरेटरों को शॉर्टकट अर्थशास्त्र रखने की अपेक्षा करेगा, और उनका कोड इस पर निर्भर हो सकता है, इसलिए अत्यधिक सलाह दी जाती है कि उन्हें कभी परिभाषित न करें।

अंकगणितीय आपरेटर

यूनरी अंकगणितीय ऑपरेटरों

यूनरी वृद्धि और कमी ऑपरेटर दोनों उपसर्ग और पोस्टफिक्स स्वाद में आते हैं। एक दूसरे से बताने के लिए, पोस्टफिक्स वेरिएंट एक अतिरिक्त डमी int तर्क लेते हैं। यदि आप वृद्धि या कमी को अधिभारित करते हैं, तो हमेशा उपसर्ग और पोस्टफिक्स संस्करण दोनों को लागू करना सुनिश्चित करें। वृद्धि के कैननिक कार्यान्वयन यहां है, कमी उसी नियमों का पालन करती है:

class X {
  X& operator++()
  {
    // do actual increment
    return *this;
  }
  X operator++(int)
  {
    X tmp(*this);
    operator++();
    return tmp;
  }
};

ध्यान दें कि उपसर्ग के संदर्भ में पोस्टफिक्स संस्करण लागू किया गया है। यह भी ध्यान रखें कि पोस्टफिक्स एक अतिरिक्त प्रति करता है। 2

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

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

बाइनरी अंकगणितीय ऑपरेटरों

द्विआधारी अंकगणितीय ऑपरेटरों के लिए, तीसरे मूल नियम ऑपरेटर ओवरलोडिंग का पालन करना न भूलें: यदि आप + प्रदान करते हैं, तो += प्रदान करते हैं, अगर आप प्रदान करते हैं - , छोड़ना नहीं -= आदि। एंड्रयू कोएनिग पहली बार कहा जाता है यह देखने के लिए कि यौगिक असाइनमेंट ऑपरेटरों को उनके गैर-कंपाउंड समकक्षों के आधार के रूप में उपयोग किया जा सकता है। यही है, ऑपरेटर + += संदर्भ में लागू किया गया है, - -= आदि के संदर्भ में लागू किया गया है।

अंगूठे के हमारे नियमों के अनुसार, + और उसके साथी गैर-सदस्य होना चाहिए, जबकि उनके यौगिक असाइनमेंट समकक्ष ( += आदि), उनके बाएं तर्क को बदलना, एक सदस्य होना चाहिए। यहां += और + के लिए अनुकरणीय कोड है, अन्य बाइनरी अंकगणितीय ऑपरेटरों को उसी तरह कार्यान्वित किया जाना चाहिए:

class X {
  X& operator+=(const X& rhs)
  {
    // actual addition of rhs to *this
    return *this;
  }
};
inline X operator+(X lhs, const X& rhs)
{
  lhs += rhs;
  return lhs;
}

operator+= प्रति परिणाम इसके परिणाम देता है, जबकि operator+ इसके परिणाम की एक प्रति देता है। बेशक, एक संदर्भ लौटने से आमतौर पर एक प्रतिलिपि वापस करने से अधिक कुशल होता है, लेकिन operator+ के मामले में, प्रतिलिपि के आसपास कोई रास्ता नहीं है। जब आप a + b लिखते हैं, तो आप परिणाम को एक नया मान मानते हैं, यही कारण है कि operator+ को एक नया मान वापस करना पड़ता है। 3 यह भी ध्यान रखें कि operator+ कॉन्स संदर्भ के बजाए प्रतिलिपि द्वारा अपना बाएं ऑपरेंड लेता है। इसके लिए कारण operator= लिए देने का कारण है operator= प्रति प्रति अपनी तर्क लेना।

बिट हेरफेर ऑपरेटर ~ & | ^ << >> अंकगणितीय ऑपरेटरों के रूप में उसी तरह लागू किया जाना चाहिए। हालांकि, (आउटपुट और इनपुट के लिए << और >> ओवरलोडिंग को छोड़कर) इन अधिभार के लिए बहुत कम उचित उपयोग मामले हैं।

3 फिर से, इस से लिया जाने वाला सबक यह है कि a += b सामान्य रूप से, a + b से अधिक कुशल होता a + b और यदि संभव हो तो उसे प्राथमिकता दी जानी चाहिए।

ऐरे सब्सक्राइबिंग

सरणी सबस्क्रिप्ट ऑपरेटर एक बाइनरी ऑपरेटर है जिसे क्लास सदस्य के रूप में कार्यान्वित किया जाना चाहिए। इसका उपयोग कंटेनर-जैसी प्रकारों के लिए किया जाता है जो किसी कुंजी द्वारा उनके डेटा तत्वों तक पहुंच की अनुमति देता है। इन्हें उपलब्ध कराने का वैचारिक रूप यह है:

class X {
        value_type& operator[](index_type idx);
  const value_type& operator[](index_type idx) const;
  // ...
};

जब तक आप नहीं चाहते कि आपकी कक्षा के उपयोगकर्ता operator[] द्वारा लौटाए गए डेटा तत्वों को बदल सकें operator[] (इस मामले में आप गैर-कॉन्स्ट संस्करण को छोड़ सकते हैं), आपको हमेशा ऑपरेटर के दोनों प्रकार प्रदान करना चाहिए।

यदि value_type को अंतर्निहित प्रकार के संदर्भ में जाना जाता है, तो ऑपरेटर के कॉन्स्ट संस्करण को कॉन्स्ट संदर्भ के बजाय प्रतिलिपि वापस करनी चाहिए।

सूचक-प्रकार के प्रकार के लिए ऑपरेटर

अपने स्वयं के इटरेटर्स या स्मार्ट पॉइंटर्स को परिभाषित करने के लिए, आपको यूनरी उपसर्ग डीरेंसेंस ऑपरेटर * और बाइनरी इंफिक्स पॉइंटर सदस्य एक्सेस ऑपरेटर -> :

class my_ptr {
        value_type& operator*();
  const value_type& operator*() const;
        value_type* operator->();
  const value_type* operator->() const;
};

ध्यान दें कि इन्हें भी हमेशा एक कॉन्स और गैर-कॉन्स संस्करण दोनों की आवश्यकता होगी। -> ऑपरेटर के लिए, यदि value_type class (या struct या union ) प्रकार का है, तो दूसरा operator->() को रिकर्सिवली कहा जाता है, जब तक कोई operator->() गैर-वर्ग प्रकार का मान देता है।

ऑपरेटर का यूनरी पता कभी अधिभारित नहीं किया जाना चाहिए।

operator->*() इस सवाल को देखें। इसका शायद ही कभी उपयोग किया जाता है और इस प्रकार शायद ही कभी अधिभारित होता है। वास्तव में, यहां तक ​​कि इटेटर भी इसे अधिभारित नहीं करते हैं।

रूपांतरण ऑपरेटर जारी रखें


रूपांतरण ऑपरेटर (जिसे उपयोगकर्ता परिभाषित रूपांतरण भी कहा जाता है)

सी ++ में आप रूपांतरण ऑपरेटर, ऑपरेटर बना सकते हैं जो संकलक को आपके प्रकार और अन्य परिभाषित प्रकारों के बीच कनवर्ट करने की अनुमति देते हैं। दो प्रकार के रूपांतरण ऑपरेटर, निहित और स्पष्ट हैं।

लागू रूपांतरण ऑपरेटर (सी ++ 98 / सी ++ 03 और सी ++ 11)

एक निहित रूपांतरण ऑपरेटर संकलक को अंतर्निहित रूप से परिवर्तित करने की अनुमति देता है (जैसे int और long बीच रूपांतरण) किसी उपयोगकर्ता द्वारा परिभाषित प्रकार के मान को किसी अन्य प्रकार में परिवर्तित करता है।

एक अंतर्निहित रूपांतरण ऑपरेटर के साथ निम्नलिखित एक साधारण वर्ग है:

class my_string {
public:
  operator const char*() const {return data_;} // This is the conversion operator
private:
  const char* data_;
};

एक-तर्क कन्स्ट्रक्टर की तरह लागू रूपांतरण ऑपरेटर उपयोगकर्ता परिभाषित रूपांतरण हैं। ओवरलोडेड फ़ंक्शन पर कॉल से मिलान करने का प्रयास करते समय कंपाइलर एक उपयोगकर्ता द्वारा परिभाषित रूपांतरण प्रदान करेगा।

void f(const char*);

my_string str;
f(str); // same as f( str.operator const char*() )

सबसे पहले यह बहुत उपयोगी लगता है, लेकिन इसके साथ समस्या यह है कि जब भी इसकी अपेक्षा नहीं की जाती है तो अंतर्निहित रूपांतरण भी इसमें आता है। निम्नलिखित कोड में, void f(const char*) my_string() void f(const char*) को कॉल किया जाएगा क्योंकि my_string() एक lvalue नहीं है, इसलिए पहला मेल नहीं खाता है:

void f(my_string&);
void f(const char*);

f(my_string());

शुरुआती आसानी से यह गलत और यहां तक ​​कि अनुभवी सी ++ प्रोग्रामर कभी-कभी आश्चर्यचकित होते हैं क्योंकि संकलक एक ओवरलोड चुनता है जिसे उन्हें संदेह नहीं था। इन समस्याओं को स्पष्ट रूपांतरण ऑपरेटरों द्वारा कम किया जा सकता है।

स्पष्ट रूपांतरण ऑपरेटर (सी ++ 11)

निहित रूपांतरण ऑपरेटर के विपरीत, जब आप उनसे अपेक्षा नहीं करते हैं तो स्पष्ट रूपांतरण ऑपरेटर कभी भी किक नहीं करेंगे। एक स्पष्ट रूपांतरण ऑपरेटर के साथ निम्नलिखित एक साधारण वर्ग है:

class my_string {
public:
  explicit operator const char*() const {return data_;}
private:
  const char* data_;
};

explicit ध्यान दें। अब जब आप निहित रूपांतरण ऑपरेटर से अप्रत्याशित कोड निष्पादित करने का प्रयास करते हैं, तो आपको एक कंपाइलर त्रुटि मिलती है:

prog.cpp: In function ‘int main()’:
prog.cpp:15:18: error: no matching function for call to ‘f(my_string)’
prog.cpp:15:18: note: candidates are:
prog.cpp:11:10: note: void f(my_string&)
prog.cpp:11:10: note:   no known conversion for argument 1 from ‘my_string’ to ‘my_string&’
prog.cpp:12:10: note: void f(const char*)
prog.cpp:12:10: note:   no known conversion for argument 1 from ‘my_string’ to ‘const char*’

स्पष्ट कास्ट ऑपरेटर का आह्वान करने के लिए, आपको static_cast , सी-स्टाइल कास्ट, या कन्स्ट्रक्टर स्टाइल कास्ट (यानी T(value) ) का उपयोग करना होगा।

हालांकि, इसमें एक अपवाद है: कंपाइलर को bool रूपांतरित करने की अनुमति है। इसके अलावा, संकलक को bool परिवर्तित होने के बाद एक और अंतर्निहित रूपांतरण करने की अनुमति नहीं है (एक कंपाइलर को एक समय में 2 अंतर्निहित रूपांतरण करने की अनुमति है, लेकिन अधिकतम पर केवल 1 उपयोगकर्ता परिभाषित रूपांतरण)।

चूंकि कंपाइलर "अतीत" bool नहीं bool , इसलिए स्पष्ट रूपांतरण ऑपरेटर अब सुरक्षित bool मुहावरे की आवश्यकता को हटा देते हैं। उदाहरण के लिए, सी ++ 11 से पहले स्मार्ट पॉइंटर्स ने सुरक्षित बूल मुहावरे को रूपांतरणों को अभिन्न प्रकारों से रोकने के लिए उपयोग किया। सी ++ 11 में, स्मार्ट पॉइंटर्स इसके बजाय एक स्पष्ट ऑपरेटर का उपयोग करते हैं क्योंकि संकलक को स्पष्ट रूप से एक प्रकार से बूल रूपांतरित करने के बाद एक अभिन्न प्रकार में परिवर्तित करने की अनुमति नहीं है।

new अधिभारित new और delete जारी रखें।


सी ++ में ऑपरेटर ओवरलोडिंग के तीन मूल नियम

जब सी ++ में ऑपरेटर ओवरलोडिंग की बात आती है, तो आपको तीन बुनियादी नियमों का पालन करना चाहिए । ऐसे सभी नियमों के साथ, वास्तव में अपवाद हैं। कभी-कभी लोग उनसे विचलित हो जाते हैं और परिणाम खराब कोड नहीं था, लेकिन ऐसे सकारात्मक विचलन कुछ और बहुत दूर हैं। कम से कम, मैंने देखा है कि 100 में से 99 विचलन में से 99 अन्यायपूर्ण थे। हालांकि, यह 1000 में से 999 भी हो सकता है। तो आप बेहतर नियमों के साथ बेहतर रहेंगे।

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

  2. हमेशा ऑपरेटर के जाने-माने अर्थशास्त्र से चिपके रहें।
    सी ++ ओवरलोडेड ऑपरेटरों के अर्थशास्त्र पर कोई सीमा नहीं है। आपका कंपाइलर खुशी से कोड स्वीकार करेगा जो बाइनरी + ऑपरेटर को अपने दाएं ऑपरेंड से घटाने के लिए लागू करता है। हालांकि, इस तरह के एक ऑपरेटर के उपयोगकर्ताओं को a + b से घटाने के लिए a + b अभिव्यक्ति पर कभी संदेह नहीं होगा। बेशक, यह मानता है कि एप्लिकेशन डोमेन में ऑपरेटर का अर्थशास्त्र निर्विवाद है।

  3. हमेशा संबंधित संचालन के एक सेट से बाहर प्रदान करें।
    ऑपरेटर एक दूसरे से और अन्य परिचालनों से संबंधित हैं । यदि आपका प्रकार a + b का समर्थन करता a + b , तो उपयोगकर्ता भी a += b को कॉल करने में सक्षम होने की उम्मीद करेंगे। यदि यह उपसर्ग वृद्धि ++a का समर्थन करता ++a , तो वे a++ काम करने की अपेक्षा करेंगे। यदि वे यह जांच सकते हैं कि a < b , वे निश्चित रूप से यह जांचने में सक्षम होंगे कि a > b । यदि वे आपके प्रकार की प्रतिलिपि बना सकते हैं, तो वे असाइनमेंट को भी काम करने की उम्मीद करते हैं।

सदस्य और गैर-सदस्य के बीच निर्णय जारी रखें।


new अधिभार और delete

नोट: यह केवल ओवरलोडिंग के सिंटैक्स से संबंधित है और delete , ऐसे अधिभारित ऑपरेटरों के कार्यान्वयन के साथ नहीं। मुझे लगता है कि ऑपरेटर अधिभार के विषय में, new अधिभार और delete के अर्थशास्त्र अपने स्वयं के एफएक्यू के लायक हैं , मैं कभी भी न्याय नहीं कर सकता।

मूल बातें

सी ++ में, जब आप new T(arg) जैसी नई अभिव्यक्ति लिखते हैं तो दो चीजें तब होती हैं जब इस अभिव्यक्ति का मूल्यांकन किया जाता है: कच्चे मेमोरी को प्राप्त करने के लिए पहले operator new लगाया जाता है, और उसके बाद इस कच्ची मेमोरी को चालू करने के लिए T के उपयुक्त कन्स्ट्रक्टर को बुलाया जाता है। वैध वस्तु इसी तरह, जब आप किसी ऑब्जेक्ट को हटाते हैं, तो पहले इसके विनाशक को बुलाया जाता है, और फिर स्मृति को operator delete वापस कर दिया जाता है।
सी ++ आपको इन दोनों परिचालनों को ट्यून करने की अनुमति देता है: स्मृति प्रबंधन और ऑब्जेक्ट का निर्माण / विनाश आवंटित स्मृति पर। उत्तरार्द्ध एक वर्ग के लिए रचनाकारों और विनाशकों को लिखकर किया जाता है। फाइन-ट्यूनिंग मेमोरी मैनेजमेंट आपके स्वयं के operator new और operator delete करके किया जाता है।

ऑपरेटर ओवरलोडिंग के बुनियादी नियमों में से पहला - ऐसा न करें - विशेष रूप से new अधिभार को delete और delete लिए लागू होता है। इन ऑपरेटरों को अधिभारित करने के लगभग एकमात्र कारण प्रदर्शन समस्याएं और स्मृति बाधाएं हैं , और कई मामलों में, अन्य क्रियाएं, जैसे कि एल्गोरिदम में किए गए परिवर्तन , स्मृति प्रबंधन को ट्विक करने के प्रयास से बहुत अधिक लागत / लाभ अनुपात प्रदान करेंगे।

सी ++ मानक पुस्तकालय पूर्वनिर्धारित new सेट के साथ आता है और ऑपरेटरों को delete है। सबसे महत्वपूर्ण ये हैं:

void* operator new(std::size_t) throw(std::bad_alloc); 
void  operator delete(void*) throw(); 
void* operator new[](std::size_t) throw(std::bad_alloc); 
void  operator delete[](void*) throw(); 

पहले दो ऑब्जेक्ट्स के लिए मेमोरी आवंटित / डिलीकेट करते हैं, बाद वाले दो ऑब्जेक्ट्स की सरणी के लिए। यदि आप इनके अपने संस्करण प्रदान करते हैं, तो वे अधिभारित नहीं होंगे , लेकिन मानक लाइब्रेरी से किसी को प्रतिस्थापित करेंगे।
यदि आप operator new ओवरलोड करते हैं, तो आपको हमेशा मिलान करने वाले operator delete ओवरलोड करना चाहिए, भले ही आप इसे कॉल करने का इरादा न रखें। इसका कारण यह है कि, यदि एक कन्स्ट्रक्टर एक नई अभिव्यक्ति के मूल्यांकन के दौरान फेंकता है, तो रन-टाइम सिस्टम operator delete को मेमोरी वापस कर देगा operator new मिलान करने के लिए जिसे ऑब्जेक्ट बनाने के लिए स्मृति आवंटित करने के लिए बुलाया गया था। यदि आप करते हैं मिलान करने वाले operator delete , डिफ़ॉल्ट को कॉल किया जाता है, जो लगभग हमेशा गलत होता है।
यदि आप new अधिभार और delete , तो आपको सरणी रूपों को ओवरलोड करने पर भी विचार करना चाहिए।

प्लेसमेंट new

सी ++ नए को अनुमति देता है और ऑपरेटरों को अतिरिक्त तर्क लेने के लिए हटा देता है।
तथाकथित प्लेसमेंट नया आपको एक निश्चित पते पर ऑब्जेक्ट बनाने की अनुमति देता है जो पास किया जाता है:

class X { /* ... */ };
char buffer[ sizeof(X) ];
void f()
{ 
  X* p = new(buffer) X(/*...*/);
  // ... 
  p->~X(); // call destructor 
} 

मानक पुस्तकालय नए के उचित अधिभार के साथ आता है और इसके लिए ऑपरेटरों को हटा देता है:

void* operator new(std::size_t,void* p) throw(std::bad_alloc); 
void  operator delete(void* p,void*) throw(); 
void* operator new[](std::size_t,void* p) throw(std::bad_alloc); 
void  operator delete[](void* p,void*) throw(); 

ध्यान दें कि, ऊपर दिए गए प्लेसमेंट के लिए उदाहरण कोड में, operator delete को कभी नहीं कहा जाता है, जब तक कि एक्स का कन्स्ट्रक्टर अपवाद फेंकता न हो।

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

कक्षा-विशिष्ट नया और हटाएं

Most commonly you will want to fine-tune memory management because measurement has shown that instances of a specific class, or of a group of related classes, are created and destroyed often and that the default memory management of the run-time system, tuned for general performance, deals inefficiently in this specific case. To improve this, you can overload new and delete for a specific class:

class my_class { 
  public: 
    // ... 
    void* operator new();
    void  operator delete(void*,std::size_t);
    void* operator new[](size_t);
    void  operator delete[](void*,std::size_t);
    // ... 
}; 

Overloaded thus, new and delete behave like static member functions. For objects of my_class , the std::size_t argument will always be sizeof(my_class) . However, these operators are also called for dynamically allocated objects of derived classes , in which case it might be greater than that.

Global new and delete

To overload the global new and delete, simply replace the pre-defined operators of the standard library with our own. However, this rarely ever needs to be done.


Why can't operator<< function for streaming objects to std::cout or to a file be a member function?

Let's say you have:

struct Foo
{
   int a;
   double b;

   std::ostream& operator<<(std::ostream& out) const
   {
      return out << a << " " << b;
   }
};

Given that, you cannot use:

Foo f = {10, 20.0};
std::cout << f;

Since operator<< is overloaded as a member function of Foo , the LHS of the operator must be a Foo object. Which means, you will be required to use:

Foo f = {10, 20.0};
f << std::cout

which is very non-intuitive.

If you define it as a non-member function,

struct Foo
{
   int a;
   double b;
};

std::ostream& operator<<(std::ostream& out, Foo const& f)
{
   return out << f.a << " " << f.b;
}

You will be able to use:

Foo f = {10, 20.0};
std::cout << f;

जो बहुत सहज है।







c++-faq