ऑपरेटर ओवरलोडिंग के लिए मूल नियम और मुहावरे क्या हैं?


2141

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

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


63
यदि हम C ++ - FAQ टैग के साथ जारी रखने जा रहे हैं, तो यह है कि प्रविष्टियों को कैसे स्वरूपित किया जाना चाहिए।
जॉन डिब्लिंग

मैंने ऑपरेटर ओवरलोडिंग के बारे में जर्मन सी ++ समुदाय के लिए लेखों की एक छोटी श्रृंखला लिखी है: भाग 1: सी ++ में ऑपरेटर ओवरलोडिंग सभी ऑपरेटरों के लिए शब्दार्थ, विशिष्ट उपयोग और विशिष्टताओं को शामिल करता है। यहां आपके जवाबों के साथ कुछ ओवरलैपिंग हैं, फिर भी कुछ अतिरिक्त जानकारी है। पार्ट्स 2 और 3 Boost.Operators का उपयोग करने के लिए एक ट्यूटोरियल बनाते हैं। क्या आप चाहेंगे कि मैं उनका अनुवाद करूँ और उन्हें उत्तर के रूप में जोड़ूँ?
Arne Mertz

ओह, और एक अंग्रेजी अनुवाद भी उपलब्ध है: मूल बातें और सामान्य अभ्यास
Arne Mertz

जवाबों:


1042

आम ऑपरेटरों को ओवरलोड करने के लिए

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

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

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

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

बिटशिफ्ट ऑपरेटर्स (स्ट्रीम I / O के लिए प्रयुक्त)

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

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

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>>, मैन्युअल रूप से स्ट्रीम की स्थिति सेट करना केवल तब आवश्यक होता है जब रीडिंग स्वयं सफल हो जाती है, लेकिन परिणाम वह नहीं होता है जो अपेक्षित होगा।

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

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

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

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

उपयोग:

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

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

तुलना ऑपरेटरों

अंगूठे के नियमों के अनुसार, बाइनरी इन्फिक्स तुलना ऑपरेटरों को गैर-सदस्य फ़ंक्शन 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 यह ध्यान दिया जाना चाहिए कि बिल्ट-इन संस्करण ||और &&शॉर्टकट शब्दार्थ का उपयोग करें। जबकि उपयोगकर्ता परिभाषित करते हैं (क्योंकि वे विधि कॉल के लिए सिंटैक्टिक चीनी हैं) शॉर्टकट शब्दार्थ का उपयोग नहीं करते हैं। उपयोगकर्ता इन ऑपरेटरों से शॉर्टकट शब्दार्थ के लिए अपेक्षा करेंगे, और उनका कोड इस पर निर्भर हो सकता है, इसलिए उन्हें परिभाषित करने के लिए यह अत्यधिक सलाह दी जाती है।

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

यूनिरी अंकगणित संचालक

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

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=

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

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

सदस्यता प्राप्त करना

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

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

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

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

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

पॉइंटर जैसे प्रकार के लिए ऑपरेटर

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

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->*()देखने के इस सवाल का । यह शायद ही कभी इस्तेमाल किया जाता है और इस तरह शायद ही कभी अतिभारित होता है। वास्तव में, यहां तक ​​कि पुनरावृत्तियां इसे अधिभार नहीं देती हैं।


रूपांतरण ऑपरेटरों के लिए जारी रखें


89
operator->()वास्तव में बेहद अजीब है। इसे वापस करने की आवश्यकता नहीं है value_type*- वास्तव में, यह एक और वर्ग प्रकार लौटा सकता है, बशर्ते कि वर्ग प्रकार एक हैoperator->() , जिसे बाद में कहा जाएगा। operator->()एक value_type*वापसी प्रकार होने तक एस की यह पुनरावर्ती कॉलिंग । पागलपन! :)
j_random_hacker

2
यह प्रभावशीलता के बारे में बिल्कुल नहीं है। इसके बारे में हम इसे (बहुत) कुछ मामलों में पारंपरिक-मुहावरेदार तरीके से नहीं कर सकते हैं: जब परिणाम की गणना करते समय दोनों ऑपरेंड की परिभाषा को अपरिवर्तित रहने की आवश्यकता होती है। और जैसा कि मैंने कहा, दो शास्त्रीय उदाहरण हैं: मैट्रिस गुणा, और बहुपद का गुणन। हम *संदर्भ में परिभाषित कर सकते हैं , *=लेकिन यह अजीब होगा क्योंकि *=एक नई वस्तु बनाने के पहले ऑपरेशन में से एक, संकलन का परिणाम होगा। फिर, Ijk लूप के बाद, हम इस अस्थायी ऑब्जेक्ट को स्वैप करेंगे *this। अर्थात। 1. कोपी, 2.ऑपरेटर *, 3.स्पैप
ल्यूक हरमिट

6
मैं आपके पॉइंटर जैसे ऑपरेटरों के कॉन्स्ट / नॉन-कास्ट संस्करणों से असहमत हूं, जैसे `कॉन्स्ट वैल्यू_टाइप एंड ऑपरेटर * (कॉस्ट);` - यह डेरेफेरिंग पर T* constलौटने जैसा होगा const T&, जो कि ऐसा नहीं है। या दूसरे शब्दों में: एक कास्ट पॉइंटर एक कॉन्टेस्ट पॉइंटर का मतलब नहीं है। वास्तव में, यह नकल करने के लिए तुच्छ नहीं है T const *- जो const_iteratorमानक पुस्तकालय में पूरे सामान का कारण है । निष्कर्ष: हस्ताक्षर होना चाहिएreference_type operator*() const; pointer_type operator->() const
Arne Mertz

6
एक टिप्पणी: बाइनरी अंकगणितीय ऑपरेटरों के कार्यान्वयन का सुझाव इतना कुशल नहीं है जितना यह हो सकता है। Se Boost ऑपरेटर्स हेडर सिमिट्री नोट: boost.org/doc/libs/1_54_0/libs/utility/operators.htm#symmetry एक और कॉपी से बचा जा सकता है यदि आप पहले पैरामीटर की एक स्थानीय कॉपी का उपयोग करते हैं, तो + =, और वापस लौटें स्थानीय प्रति। यह NRVO ऑप्टिमाइज़ेशन को सक्षम बनाता है।
मनु ३४३ Man२६

3
जैसा कि मैंने चैट में उल्लेख किया है, के बजाय के L <= Rरूप में भी व्यक्त किया जा सकता !(R < L)है !(L > R)। हार्ड-टू-ऑप्टिमाइज़ेशन अभिव्यक्तियों में इनलाइनिंग की एक अतिरिक्त परत को बचा सकता है (और यह भी कि Boost.Operators इसे कैसे लागू करते हैं)।
TemplateRex

494

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

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

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

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

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


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


16
केवल एक चीज जिससे मैं वाकिफ हूं जो इनमें से किसी का भी उल्लंघन करती है boost::spirit
बिली ओनेल

66
@ बिली: कुछ के अनुसार, +स्ट्रिंग के संयोजन के लिए गाली देना एक उल्लंघन है, लेकिन यह अब तक अच्छी तरह से स्थापित प्रैक्सिस बन गया है, जिससे यह स्वाभाविक लगता है। हालाँकि मुझे 90 के दशक में देखा गया एक घरेलू काढ़ा स्ट्रिंग वर्ग याद है जिसने &इस उद्देश्य के लिए द्विआधारी का उपयोग किया था (स्थापित प्रैक्सिस के लिए BASIC का जिक्र)। लेकिन, हाँ, इसे std lib में डालने से मूल रूप से यह पत्थर में सेट हो जाता है। वही गाली <<देने के >>लिए और IO, BTW के लिए जाता है । बाएं-शिफ्टिंग स्पष्ट आउटपुट ऑपरेशन क्यों होगा? क्योंकि हम सभी ने इसके बारे में तब सीखा जब हमने अपना पहला "हैलो, दुनिया!" देखा। आवेदन। और किसी अन्य कारण से नहीं।
sbi

5
@ कुरसी: यदि आपको इसे समझाना है, तो यह स्पष्ट रूप से स्पष्ट और निर्विवाद नहीं है। इसी तरह अगर आपको ओवरलोडिंग पर चर्चा करने या बचाव करने की आवश्यकता है।
sbi

5
@ एसएसबी: "सहकर्मी समीक्षा" हमेशा एक अच्छा विचार है। मेरे लिए एक बुरी तरह से चुना हुआ ऑपरेटर एक बुरी तरह से चुना समारोह नाम से अलग नहीं है (मैंने कई देखा)। ऑपरेटर सिर्फ कार्य हैं। न आधिक न कम। नियम सिर्फ एक ही हैं। और यह समझने के लिए कि क्या कोई विचार अच्छा है, सबसे अच्छा तरीका यह समझा जाता है कि समझने में कितना समय लगता है। (इसलिए, सहकर्मी की समीक्षा एक जरूरी है, लेकिन साथियों को कुत्तों और पूर्वाग्रहों से मुक्त लोगों के बीच चुना जाना चाहिए।)
एमिलियो गरवाग्लिया

5
मेरे लिए, मेरे बारे में एकमात्र बिल्कुल स्पष्ट और निर्विवाद तथ्य operator==यह है कि यह एक समतुल्य संबंध होना चाहिए (IOW, आपको गैर संकेतन NaN का उपयोग नहीं करना चाहिए)। कंटेनरों पर कई उपयोगी तुल्यता संबंध हैं। समानता का क्या अर्थ है? " aबराबर b" का मतलब है किa और bएक ही गणितीय मूल्य है। एक (गैर-NaN) के गणितीय मूल्य की अवधारणा floatस्पष्ट है, लेकिन एक कंटेनर के गणितीय मूल्य की कई अलग-अलग (प्रकार पुनरावर्ती) उपयोगी परिभाषाएं हो सकती हैं। समानता की सबसे मजबूत परिभाषा "वे एक ही वस्तु हैं", और यह बेकार है।
जिज्ञासु

265

ऑपरेटर का सामान्य सिंटेक्स C ++ में ओवरलोडिंग

आप C ++ में निर्मित प्रकारों के लिए ऑपरेटरों का अर्थ नहीं बदल सकते हैं, ऑपरेटर केवल उपयोगकर्ता-परिभाषित प्रकार 1 के लिए अतिभारित हो सकते हैं । यही है, कम से कम ऑपरेंड में से एक उपयोगकर्ता-परिभाषित प्रकार का होना चाहिए। अन्य ओवरलोड कार्यों के साथ, ऑपरेटरों को केवल एक बार मापदंडों के एक निश्चित सेट के लिए ओवरलोड किया जा सकता है।

सभी ऑपरेटरों को C ++ में ओवरलोड नहीं किया जा सकता है। जिन ऑपरेटरों को ओवरलोड नहीं किया जा सकता है, वे हैं: . :: sizeof typeid .*और C ++ में एकमात्र टर्नरी ऑपरेटर,?:

C ++ में ओवरलोड हो सकने वाले ऑपरेटरों में ये हैं:

  • अंकगणित ऑपरेटर: + - * / %और+= -= *= /= %= (सभी बाइनरी इन्फिक्स); + -(unary prefix); ++ --(यूनिकरी प्रीफिक्स और पोस्टफिक्स)
  • बिट हेरफेर: & | ^ << >> और &= |= ^= <<= >>=(सभी बाइनरी इन्फिक्स); ~(एकांत उपसर्ग)
  • बूलियन बीजगणित: == != < > <= >= || && (सभी बाइनरी इन्फिक्स); !(एकांत उपसर्ग)
  • स्मृति प्रबंधन: new new[] delete delete[]
  • अंतर्निहित रूपांतरण ऑपरेटर
  • मिश्रण: = [] -> ->* , (सभी बाइनरी इन्फिक्स); * &(सभी अपरिपक्व उपसर्ग) ()(फ़ंक्शन कॉल, एन-एरी इन्फिक्स)

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

C ++ में, ऑपरेटरों को विशेष नामों के साथ फ़ंक्शन के रूप में ओवरलोड किया जाता है । अन्य कार्यों की तरह, ओवरलोड ऑपरेटरों को आम तौर पर या तो उनके बाएं ऑपरेंड के प्रकार के सदस्य फ़ंक्शन के रूप में या गैर-सदस्य कार्यों के रूप में लागू किया जा सकता है । चाहे आप चुनने के लिए स्वतंत्र हों या किसी एक का उपयोग करने के लिए बाध्य हों, कई मानदंडों पर निर्भर करता है। 2 एक ऑब्जेक्ट के लिए लागू किया गया एक unary ऑपरेटर @3 , operator@(x)या तो या के रूप में लागू किया जाता है x.operator@()। एक बाइनरी इन्फिक्स ऑपरेटर @, वस्तुओं पर लागू होता है xऔरy , के रूप में या तो कहा जाता है operator@(x,y)या के रूप में x.operator@(y)4

गैर-सदस्य कार्यों के रूप में कार्यान्वित किए जाने वाले ऑपरेटर कभी-कभी उनके ऑपरेंड के प्रकार के मित्र होते हैं।

1 शब्द "उपयोगकर्ता-परिभाषित" थोड़ा भ्रामक हो सकता है। C ++ अंतर्निहित प्रकारों और उपयोगकर्ता-परिभाषित प्रकारों के बीच अंतर करता है। पूर्व के लिए उदाहरण के लिए int, char और double; उत्तरार्द्ध सभी संरचना, वर्ग, संघ, और एनम प्रकार के होते हैं, जिनमें मानक पुस्तकालय से शामिल हैं, भले ही वे इस तरह के उपयोगकर्ताओं द्वारा परिभाषित नहीं हैं।

2 इसमें कवर किया गया है अक्सर पूछे जाने वाले प्रश्न के बाद के भाग

3 @ सी में एक वैध ऑपरेटर ++, जिसके कारण मैं एक प्लेसहोल्डर के रूप में उपयोग नहीं है।

4 C ++ में केवल टर्नेरी ऑपरेटर को ओवरलोड नहीं किया जा सकता है और केवल n-ary ऑपरेटर को हमेशा सदस्य फ़ंक्शन के रूप में लागू किया जाना चाहिए।


सी ++ में ऑपरेटर ओवरलोडिंग के तीन बुनियादी नियमों को जारी रखें ।


~एकरी उपसर्ग है, न कि बाइनरी इन्फिक्स।
mrkj

1
.*गैर-अधिभार ऑपरेटरों की सूची से गायब है।
celticminstrel

1
@ मैं एक वास्तविक ऑपरेटर के बजाय एक प्लेसहोल्डर का उपयोग करना चाहता था ताकि यह स्पष्ट हो सके कि यह एक विशेष ऑपरेटर के बारे में नहीं है, लेकिन उन सभी पर लागू होता है। और, यदि आप C ++ प्रोग्रामर बनना चाहते हैं, तो आपको छोटे प्रिंट पर भी ध्यान देना सीखना चाहिए। :)
sbi

1
@ एचआर: क्या आपने इस गाइड को पढ़ा है, तो आपको पता होगा कि क्या गलत है। मैं आमतौर पर सुझाव देता हूं कि आपको प्रश्न से जुड़े पहले तीन उत्तरों को पढ़ना चाहिए। यह आपके जीवन के आधे घंटे से अधिक नहीं होना चाहिए, और आपको एक बुनियादी समझ देता है। ऑपरेटर-विशिष्ट सिंटैक्स आप बाद में देख सकते हैं। आपकी विशिष्ट समस्या बताती है कि आप operator+()एक सदस्य फ़ंक्शन के रूप में अधिभार लगाने की कोशिश करते हैं, लेकिन इसे एक मुफ्त फ़ंक्शन के हस्ताक्षर दिए। देखें यहाँ
sbi

1
@ एसएसबी: मैंने पहले तीन पोस्ट पढ़ी हैं और उन्हें बनाने के लिए धन्यवाद। :) मैं समस्या को हल करने की कोशिश करूंगा अन्यथा मुझे लगता है कि इसे एक अलग प्रश्न पर पूछना बेहतर है। हमारे लिए जीवन को इतना आसान बनाने के लिए फिर से धन्यवाद! : डी
होसिन रहमाना १४'१

251

सदस्य और गैर-सदस्य के बीच निर्णय

बाइनरी ऑपरेटर =(असाइनमेंट), [](एरे सबस्क्रिप्शन), ->(मेंबर एक्सेस), साथ ही एन-एरी ()(फंक्शन कॉल) ऑपरेटर को हमेशा सदस्य कार्यों के रूप में लागू किया जाना चाहिए , क्योंकि भाषा के सिंटैक्स के लिए उनकी आवश्यकता होती है।

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

उन सभी ऑपरेटरों के लिए जहां आपको सदस्य कार्य या गैर-सदस्यीय फ़ंक्शन के रूप में उन्हें लागू करने के लिए चुनना है, यह तय करने के लिए अंगूठे के निम्नलिखित नियमों का उपयोग करें :

  1. यदि यह एक अपर ऑपरेटर है , तो इसे एक सदस्य फ़ंक्शन के रूप में लागू करें ।
  2. यदि एक बाइनरी ऑपरेटर दोनों ऑपरेंड को समान रूप से मानता है (यह उन्हें अपरिवर्तित छोड़ देता है), तो इस ऑपरेटर को गैर-सदस्य के रूप में लागू करें फ़ंक्शन के ।
  3. यदि एक बाइनरी ऑपरेटर अपने दोनों ऑपरेंड्स के साथ समान रूप से व्यवहार नहीं करता है (आमतौर पर यह उसके बाएं ऑपरेंड को बदल देगा), तो यह उसके बाएं ऑपरेंड के प्रकार का सदस्य फ़ंक्शन बनाने के लिए उपयोगी हो सकता है , अगर उसे ऑपरेंड के निजी भागों तक पहुंचना है।

बेशक, अंगूठे के सभी नियमों के साथ, अपवाद हैं। यदि आपके पास एक प्रकार है

enum Month {Jan, Feb, ..., Nov, Dec}

और आप इसके लिए वेतन वृद्धि और वेतन वृद्धि ऑपरेटरों को ओवरलोड करना चाहते हैं, आप इसे सदस्य फ़ंक्शन के रूप में नहीं कर सकते, क्योंकि C ++ में, enum प्रकारों में सदस्य फ़ंक्शन नहीं हो सकते। इसलिए आपको इसे एक मुफ्त फ़ंक्शन के रूप में अधिभार देना होगा। तथाoperator<() एक वर्ग टेम्पलेट के लिए नेस्टेड क्लास टेम्पलेट के लिए वर्ग परिभाषा में सदस्य फ़ंक्शन इनलाइन के रूप में लिखने और पढ़ने में बहुत आसान है। लेकिन ये वास्तव में दुर्लभ अपवाद हैं।

(हालांकि, यदि आप एक अपवाद बनाते हैं, तो ऑपरेटर के constलिए -ness के मुद्दे को मत भूलना , जो सदस्य कार्यों के लिए, निहितार्थ thisतर्क बन जाता है। यदि गैर-सदस्य फ़ंक्शन के रूप में ऑपरेटर constसंदर्भ के रूप में इसके बाएं-सबसे तर्क को ले जाएगा। , एक सदस्य समारोह के रूप में एक ही ऑपरेटर एक संदर्भ constबनाने के लिए अंत में होना चाहिए ।)*thisconst


सामान्य ऑपरेटरों को ओवरलोड करने के लिए जारी रखें ।


9
प्रभावी सी ++ में हर्ब सटर का आइटम (या यह सी ++ कोडिंग मानक है?) का कहना है कि किसी को सदस्य के कार्यों के लिए गैर-सदस्य गैर-मित्र कार्यों को प्राथमिकता देना चाहिए, ताकि कक्षा के इनकैप्सुलेशन को बढ़ाया जा सके। IMHO, एन्कैप्सुलेशन कारण आपके अंगूठे के नियम के लिए पूर्वता लेता है, लेकिन यह आपके अंगूठे के नियम के गुणवत्ता मूल्य को कम नहीं करता है।
पियरसबल

8
@paercebal: प्रभावी सी ++ , मेयर्स द्वारा है, सी ++ सीटर द्वारा कोडिंग मानक । आप किसका जिक्र कर रहे हैं? वैसे भी, मैं operator+=()एक सदस्य नहीं होने के विचार को नापसंद करता हूं । इसे अपने बाएं हाथ के ऑपरेंड को बदलना होगा, इसलिए परिभाषा के अनुसार इसे अपने इनर में गहरी खुदाई करनी होगी। इसे सदस्य नहीं बनाकर आप क्या हासिल करेंगे?
sbi

9
@sbi: C ++ कोडिंग स्टैंडर्ड्स (सटर) में आइटम 44 , नॉनमेम्बर नॉनफ्रेंड फ़ंक्शंस लिखना पसंद करते हैं , बेशक, यह केवल तभी लागू होता है जब आप वास्तव में इस फ़ंक्शन को केवल क्लास के सार्वजनिक इंटरफ़ेस का उपयोग करके लिख सकते हैं। यदि आप (या नहीं कर सकते हैं लेकिन यह प्रदर्शन को बुरी तरह से बाधित करेगा), तो आपको इसे सदस्य या मित्र बनाना होगा।
Matthieu M.

3
@ एसबी: उफ़, प्रभावी, असाधारण ... कोई आश्चर्य नहीं कि मैं नामों को मिलाता हूं। वैसे भी लाभ एक निजी या संरक्षित डेटा तक पहुंच वाले कार्यों की संख्या को यथासंभव सीमित करना है। इस तरह, आप अपनी कक्षा के इनकैप्सुलेशन को बढ़ाते हैं, जिससे इसका रखरखाव / परीक्षण / विकास आसान हो जाता है।
पियरसबल

12
@ एसएसबी: एक उदाहरण। मान लें कि आप स्ट्रिंग क्लास को कोड कर रहे हैं, दोनों तरीकों operator +=और appendतरीकों के साथ। appendविधि अधिक पूरा हो गया है, क्योंकि आप मैं सूचकांक n -1 सूचकांक से पैरामीटर की सबस्ट्रिंग संलग्न कर सकते हैं: append(string, start, end)यह है करने के लिए तार्किक लगता है +=के साथ कॉल संलग्न start = 0और end = string.size। उस समय, परिशिष्ट सदस्य विधि हो सकती है, लेकिन operator +=उसे सदस्य होने की आवश्यकता नहीं है, और इसे गैर-सदस्य बनाने से कोडिंग स्ट्रिंग के साथ खेलने की मात्रा कम हो जाएगी, इसलिए यह एक अच्छी बात है ...। ^ _ ^ ...
पीरसेबल

165

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

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

प्रभावशाली रूपांतरण ऑपरेटर (C ++ 98 / C ++ 03 और C ++ 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(my_string&);
void f(const char*);

f(my_string());

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

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

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

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

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

prog.cpp: फ़ंक्शन में 'int main ()':
prog.cpp: 15: 18: त्रुटि: 'f (my_string)' के लिए कॉल के लिए कोई मेल नहीं खाता
prog.cpp: 15: 18: नोट: उम्मीदवार हैं:
prog.cpp: 11: 10: नोट: शून्य f (my_string &)
prog.cpp: 11: 10: नोट: तर्क 1 के लिए 'my_string' से 'my_string' तक कोई ज्ञात रूपांतरण नहीं
prog.cpp: 12: 10: नोट: शून्य च (कास्ट चार *)
prog.cpp: 12: 10: नोट: तर्क 1 के लिए 'my_string' से 'const char' के लिए कोई ज्ञात रूपांतरण नहीं

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

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

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

ओवरलोडिंगnewdelete जारी रखें और


148

ओवरलोडिंग newऔरdelete

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

मूल बातें

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

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

C ++ मानक पुस्तकालय पूर्वनिर्धारित 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

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

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अन्य तर्कों के साथ भी कर सकते हैं । प्लेसमेंट नए के लिए अतिरिक्त तर्क के रूप में, ये तर्क कीवर्ड के बाद कोष्ठक के भीतर भी सूचीबद्ध हैं new। ऐतिहासिक कारणों से, इस तरह के वेरिएंट को अक्सर प्लेसमेंट नया भी कहा जाता है, भले ही उनके तर्क किसी विशिष्ट पते पर किसी वस्तु को रखने के लिए न हों।

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

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

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);
    // ... 
}; 

इस प्रकार अतिभारित, नए और हटाए जाने वाले स्थिर सदस्य कार्यों की तरह व्यवहार करते हैं। की वस्तुओं के लिए my_class, std::size_tतर्क हमेशा रहेगा sizeof(my_class)। हालांकि, इन ऑपरेटरों को व्युत्पन्न वर्गों की गतिशील रूप से आवंटित वस्तुओं के लिए भी बुलाया जाता है , इस स्थिति में यह इससे अधिक हो सकता है।

वैश्विक नया और हटाना

वैश्विक नए को ओवरलोड करने और हटाने के लिए, बस मानक पुस्तकालय के पूर्व-परिभाषित ऑपरेटरों को हमारे स्वयं के साथ बदलें। हालांकि, यह शायद ही कभी किया जाना चाहिए।


11
मैं इस बात से भी सहमत नहीं हूं कि वैश्विक ऑपरेटर को नया और हटाना आमतौर पर प्रदर्शन के लिए है: इसके विपरीत, यह आमतौर पर बग ट्रेसिंग के लिए होता है।
यत्रिकाल

1
आपको यह भी ध्यान देना चाहिए, कि यदि आप एक अतिभारित नए ऑपरेटर का उपयोग करते हैं, तो आपको मिलान तर्क के साथ एक डिलीट ऑपरेटर भी प्रदान करना होगा। आप कहते हैं कि वैश्विक नए / हटाए जाने वाले अनुभाग में जहां यह बहुत अधिक रुचि नहीं है।
यट्रिल

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

1
@sbi: सी (या बेहतर, के लिए लिंक) gotw.ca/publications/mill15.htm । यह केवल उन लोगों के प्रति अच्छा व्यवहार है जो कभी-कभी nothrowनए प्रयोग करते हैं ।
अलेक्जेंड्रे सी।

1
"यदि आप एक मेल ऑपरेटर को डिलीट नहीं करते हैं, तो डिफॉल्ट को एक कहा जाता है" -> दरअसल, यदि आप कोई तर्क जोड़ते हैं और एक मेल डिलीट नहीं करते हैं, तो कोई भी ऑपरेटर डिलीट नहीं होता है, और आपके पास मेमोरी लीक होती है। (१५.२.२, ऑब्जेक्ट द्वारा कब्जे में लिया गया भंडारण केवल तभी उचित माना जाता है जब ... ऑपरेटर डिलीट पाया जाता है)
डैस्कैंडी २३'१५

46

operator<<ऑब्जेक्ट को स्ट्रीम करने std::coutया फ़ाइल में सदस्य फ़ंक्शन करने के लिए फ़ंक्शन क्यों नहीं किया जा सकता है?

मान लीजिए कि आपके पास है:

struct Foo
{
   int a;
   double b;

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

यह देखते हुए कि आप उपयोग नहीं कर सकते हैं:

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

चूंकि operator<<सदस्य के कार्य के रूप में ओवरलोड किया गया है Foo, ऑपरेटर के एलएचएस को एक Fooवस्तु होना चाहिए । जिसका अर्थ है, आपको उपयोग करने की आवश्यकता होगी:

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

जो बहुत गैर-सहज है।

यदि आप इसे गैर-सदस्य फ़ंक्शन के रूप में परिभाषित करते हैं,

struct Foo
{
   int a;
   double b;
};

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

आप का उपयोग करने में सक्षम हो जाएगा:

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

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

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