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




operators operator-overloading c++-faq (7)

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

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

लागू रूपांतरण ऑपरेटर (सी ++ 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 जारी रखें।

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

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


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.


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

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

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

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

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

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

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

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

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

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


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

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

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

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

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

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


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

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

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


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

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

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

ऑपरेटरों में से जिन्हें सी ++ में ओवरलोड किया जा सकता है ये हैं:

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

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

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

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

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

2 यह इस FAQ के बाद के हिस्से में शामिल है।

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

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

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


परिचय

सी ++ वैल्यू सेमेन्टिक्स के साथ उपयोगकर्ता परिभाषित प्रकारों के चर का व्यवहार करता है। इसका मतलब है कि वस्तुओं को विभिन्न संदर्भों में पूरी तरह से कॉपी किया गया है, और हमें समझना चाहिए कि "ऑब्जेक्ट की प्रतिलिपि बनाना" वास्तव में क्या है।

आइए एक साधारण उदाहरण पर विचार करें:

class person
{
    std::string name;
    int age;

public:

    person(const std::string& name, int age) : name(name), age(age)
    {
    }
};

int main()
{
    person a("Bjarne Stroustrup", 60);
    person b(a);   // What happens here?
    b = a;         // And here?
}

(यदि आप name(name), age(age) भाग से परेशान हैं, तो इसे सदस्य प्रारंभकर्ता सूची कहा जाता है।)

विशेष सदस्य कार्य

किसी person ऑब्जेक्ट की प्रतिलिपि बनाने का क्या अर्थ है? main समारोह दो विशिष्ट प्रतिलिपि परिदृश्य दिखाता है। प्रारंभिक person b(a); कॉपी कन्स्ट्रक्टर द्वारा किया जाता है। इसका काम मौजूदा वस्तु की स्थिति के आधार पर एक ताजा वस्तु बनाना है। असाइनमेंट b = a कॉपी असाइनमेंट ऑपरेटर द्वारा किया जाता है। इसका काम आम तौर पर थोड़ा अधिक जटिल होता है, क्योंकि लक्ष्य वस्तु पहले से ही कुछ मान्य राज्य में है जिसके साथ निपटा जाना आवश्यक है।

चूंकि हमने न तो कॉपी कन्स्ट्रक्टर और न ही असाइनमेंट ऑपरेटर (न ही विनाशक) घोषित किया है, इन्हें हमारे लिए निहित रूप से परिभाषित किया गया है। मानक से उद्धरण:

[...] प्रतिलिपि निर्माता और कॉपी असाइनमेंट ऑपरेटर, [...] और विनाशक विशेष सदस्य कार्य हैं। [ नोट : कार्यान्वयन इन वर्ग कार्यों को कुछ वर्ग प्रकारों के लिए घोषित करेगा जब कार्यक्रम स्पष्ट रूप से उन्हें घोषित नहीं करता है। अगर उनका उपयोग किया जाता है तो कार्यान्वयन उन्हें निश्चित रूप से परिभाषित करेगा। [...] अंत नोट ] [n3126.pdf धारा 12 §1]

डिफ़ॉल्ट रूप से, किसी ऑब्जेक्ट की प्रतिलिपि बनाने का अर्थ है अपने सदस्यों की प्रतिलिपि बनाना:

गैर-यूनियन क्लास एक्स के लिए अंतर्निहित रूप से परिभाषित प्रतिलिपि निर्माता अपने उप-प्रोजेक्ट की सदस्यवाही प्रतिलिपि करता है। [n3126.pdf अनुभाग 12.8 §16]

गैर-यूनियन क्लास एक्स के लिए अंतर्निहित रूप से परिभाषित प्रति असाइनमेंट ऑपरेटर अपने उप-प्रोजेक्ट्स के सदस्यवार प्रति असाइनमेंट करता है। [n3126.pdf सेक्शन 12.8 §30]

लागू परिभाषाएं

person लिए अंतर्निहित परिभाषित विशेष सदस्य कार्य इस तरह दिखते हैं:

// 1. copy constructor
person(const person& that) : name(that.name), age(that.age)
{
}

// 2. copy assignment operator
person& operator=(const person& that)
{
    name = that.name;
    age = that.age;
    return *this;
}

// 3. destructor
~person()
{
}

सदस्यवार प्रतिलिपि वही है जो हम इस मामले में चाहते हैं: name और age की प्रतिलिपि बनाई गई है, इसलिए हमें एक आत्मनिर्भर, स्वतंत्र person वस्तु मिलती है। निहित रूप से परिभाषित विनाशक हमेशा खाली रहता है। यह इस मामले में भी ठीक है क्योंकि हमने निर्माता में कोई संसाधन नहीं प्राप्त किया है। person विनाशक समाप्त होने के बाद सदस्यों के विनाशकों को पूरी तरह से बुलाया जाता है:

विनाशक के शरीर को निष्पादित करने और शरीर के भीतर आवंटित किसी भी स्वचालित वस्तुओं को नष्ट करने के बाद, कक्षा X के लिए एक विनाशक एक्स के प्रत्यक्ष [...] सदस्यों [n3126.pdf 12.4 §6] के लिए विनाशकों को बुलाता है

संसाधनों का प्रबंधन

तो हमें उन विशेष सदस्य कार्यों को स्पष्ट रूप से कब घोषित करना चाहिए? जब हमारी कक्षा संसाधन का प्रबंधन करती है , यानी, जब उस संसाधन के लिए कक्षा का कोई उद्देश्य जिम्मेदार होता है। इसका आमतौर पर अर्थ है कि संसाधन कन्स्ट्रक्टर (या कन्स्ट्रक्टर में पारित) में अधिग्रहण किया जाता है और विनाशक में जारी किया जाता है।

आइए प्री-स्टैंडर्ड सी ++ में समय पर वापस जाएं। std::string जैसी कोई चीज़ नहीं थी, और प्रोग्रामर पॉइंटर्स से प्यार करते थे। person वर्ग इस तरह दिख सकता है:

class person
{
    char* name;
    int age;

public:

    // the constructor acquires a resource:
    // in this case, dynamic memory obtained via new[]
    person(const char* the_name, int the_age)
    {
        name = new char[strlen(the_name) + 1];
        strcpy(name, the_name);
        age = the_age;
    }

    // the destructor must release this resource via delete[]
    ~person()
    {
        delete[] name;
    }
};

आज भी, लोग अभी भी इस शैली में कक्षाएं लिखते हैं और परेशानी में पड़ते हैं: " मैंने एक व्यक्ति को वेक्टर में धक्का दिया और अब मुझे पागल मेमोरी त्रुटियां मिलती हैं! " याद रखें कि डिफ़ॉल्ट रूप से, ऑब्जेक्ट की प्रतिलिपि बनाने का अर्थ है अपने सदस्यों की प्रतिलिपि बनाना, लेकिन name सदस्य की प्रतिलिपि बनाना केवल एक सूचक की प्रतिलिपि बनाता है, कि चरित्र सरणी को इंगित करता है! इसमें कई अप्रिय प्रभाव हैं:

  1. b माध्यम से a माध्यम से परिवर्तन देखा जा सकता है।
  2. एक बार b नष्ट हो जाने के बाद, a.name एक a.name सूचक है।
  3. यदि a नष्ट हो गया है, तो खतरनाक सूचक को अपरिभाषित व्यवहार को कम करना
  4. चूंकि असाइनमेंट ध्यान में नहीं आता है कि असाइनमेंट से पहले किस name ओर इशारा किया गया है, जल्दी या बाद में आपको जगह पर मेमोरी लीक मिल जाएगी।

स्पष्ट परिभाषाएं

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

// 1. copy constructor
person(const person& that)
{
    name = new char[strlen(that.name) + 1];
    strcpy(name, that.name);
    age = that.age;
}

// 2. copy assignment operator
person& operator=(const person& that)
{
    if (this != &that)
    {
        delete[] name;
        // This is a dangerous point in the flow of execution!
        // We have temporarily invalidated the class invariants,
        // and the next statement might throw an exception,
        // leaving the object in an invalid state :(
        name = new char[strlen(that.name) + 1];
        strcpy(name, that.name);
        age = that.age;
    }
    return *this;
}

प्रारंभिकरण और असाइनमेंट के बीच अंतर ध्यान दें: स्मृति रिसाव को रोकने के लिए name देने से पहले हमें पुराने राज्य को फाड़ना होगा। साथ ही, हमें फॉर्म x = x स्व-असाइनमेंट के खिलाफ सुरक्षा करना है। उस चेक के बिना, delete[] name स्रोत स्ट्रिंग युक्त सरणी को हटा देगा, क्योंकि जब आप x = x that.name हैं, तो that.name this->name और that.name दोनों में एक ही सूचक होता है।

अपवाद सुरक्षा

दुर्भाग्यवश, यह समाधान विफल हो जाएगा यदि new char[...] स्मृति थकावट के कारण अपवाद फेंकता है। एक संभावित समाधान स्थानीय चर को पेश करना और बयानों को पुन: व्यवस्थित करना है:

// 2. copy assignment operator
person& operator=(const person& that)
{
    char* local_name = new char[strlen(that.name) + 1];
    // If the above statement throws,
    // the object is still in the same state as before.
    // None of the following statements will throw an exception :)
    strcpy(local_name, that.name);
    delete[] name;
    name = local_name;
    age = that.age;
    return *this;
}

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

अपरिवर्तनीय संसाधन

कुछ संसाधन कॉपी नहीं किए जा सकते हैं या नहीं, जैसे फाइल हैंडल या म्यूटेक्स। उस स्थिति में, प्रतिलिपि देने के बिना कॉपी कन्स्ट्रक्टर और कॉपी असाइनमेंट ऑपरेटर को private रूप से घोषित करें:

private:

    person(const person& that);
    person& operator=(const person& that);

वैकल्पिक रूप से, आप boost::noncopyable से प्राप्त कर सकते हैं या उन्हें हटाए गए (C ++ 0x) घोषित कर सकते हैं:

person(const person& that) = delete;
person& operator=(const person& that) = delete;

तीन का शासन

कभी-कभी आपको एक ऐसी कक्षा को लागू करने की आवश्यकता होती है जो संसाधन का प्रबंधन करे। (कभी भी एक वर्ग में एकाधिक संसाधनों का प्रबंधन न करें, इससे केवल दर्द हो सकता है।) उस स्थिति में, तीन के नियम को याद रखें:

यदि आपको या तो विनाशक घोषित करने की आवश्यकता है, तो कन्स्ट्रक्टर कॉपी करें या खुद को असाइनमेंट ऑपरेटर कॉपी करें, आपको शायद उन सभी तीनों को स्पष्ट रूप से घोषित करने की आवश्यकता है।

(दुर्भाग्यवश, यह "नियम" सी ++ मानक या किसी भी कंपाइलर द्वारा लागू नहीं है जिसे मैं जानता हूं।)

सलाह

अधिकांश समय, आपको संसाधन को स्वयं प्रबंधित करने की आवश्यकता नहीं होती है, क्योंकि मौजूदा श्रेणी जैसे std::string पहले से ही आपके लिए करता है। एक char* का उपयोग करके एक std::string सदस्य का उपयोग करके सरल कोड की तुलना करें और char* का उपयोग करके त्रुटि-प्रवण विकल्प पर क्लिक करें और आपको आश्वस्त होना चाहिए। जब तक आप कच्चे सूचक सदस्यों से दूर रहें, तब तक तीन का नियम आपके स्वयं के कोड से संबंधित होने की संभावना नहीं है।





c++ operators operator-overloading c++-faq