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