C++
तीन, पाँच, और शून्य का नियम
खोज…
पाँच का नियम
C ++ 11 दो नए विशेष सदस्य कार्यों का परिचय देता है: मूव कंस्ट्रक्टर और मूव असाइनमेंट ऑपरेटर। उन सभी कारणों के लिए जिन्हें आप C ++ 03 में तीन के नियम का पालन करना चाहते हैं, आप आमतौर पर C ++ 11 में पांच के नियम का पालन करना चाहते हैं: यदि किसी वर्ग को पांच विशेष सदस्य कार्यों में से एक की आवश्यकता होती है, और यदि शब्दार्थ को स्थानांतरित करना है वांछित हैं, तो यह उनमें से सभी की आवश्यकता है।
ध्यान दें, हालांकि, पांच के नियम का पालन करने में विफल रहने को आमतौर पर एक त्रुटि नहीं माना जाता है, लेकिन एक चूक अनुकूलन अवसर, जब तक कि तीन के नियम का पालन किया जाता है। अगर कंपाइलर या मूव असाइनमेंट ऑपरेटर कोई भी उपलब्ध नहीं है, जब कंपाइलर सामान्य रूप से एक का उपयोग करेगा, तो यह संभव हो सके तो कॉपी सिमेंटिक्स का उपयोग करेगा, जिसके परिणामस्वरूप अनावश्यक कॉपी ऑपरेशन के कारण कम कुशल संचालन होगा। यदि चालित शब्दार्थ एक वर्ग के लिए वांछित नहीं हैं, तो उसे मूव कंस्ट्रक्टर या असाइनमेंट ऑपरेटर घोषित करने की कोई आवश्यकता नहीं है।
तीन के नियम के समान उदाहरण:
class Person
{
char* name;
int age;
public:
// Destructor
~Person() { delete [] name; }
// Implement Copy Semantics
Person(Person const& other)
: name(new char[std::strlen(other.name) + 1])
, age(other.age)
{
std::strcpy(name, other.name);
}
Person &operator=(Person const& other)
{
// Use copy and swap idiom to implement assignment.
Person copy(other);
swap(*this, copy);
return *this;
}
// Implement Move Semantics
// Note: It is usually best to mark move operators as noexcept
// This allows certain optimizations in the standard library
// when the class is used in a container.
Person(Person&& that) noexcept
: name(nullptr) // Set the state so we know it is undefined
, age(0)
{
swap(*this, that);
}
Person& operator=(Person&& that) noexcept
{
swap(*this, that);
return *this;
}
friend void swap(Person& lhs, Person& rhs) noexcept
{
std::swap(lhs.name, rhs.name);
std::swap(lhs.age, rhs.age);
}
};
वैकल्पिक रूप से, प्रतिलिपि और चाल असाइनमेंट ऑपरेटर दोनों को एकल असाइनमेंट ऑपरेटर के साथ प्रतिस्थापित किया जा सकता है, जो कॉपी-एंड-स्वैप मुहावरे का उपयोग करने की सुविधा के लिए संदर्भ या प्रतिद्वंद्वि संदर्भ के बजाय मूल्य द्वारा एक उदाहरण लेता है।
Person& operator=(Person copy)
{
swap(*this, copy);
return *this;
}
प्रदर्शन के कारणों के लिए तीन के नियम से पांच के नियम में विस्तार करना महत्वपूर्ण है, लेकिन ज्यादातर मामलों में कड़ाई से आवश्यक नहीं है। कॉपी कंस्ट्रक्टर और असाइनमेंट ऑपरेटर को जोड़ना यह सुनिश्चित करता है कि प्रकार हिलाने से मेमोरी लीक नहीं होगी (मूव-कंस्ट्रक्शन बस उस स्थिति में कॉपी करने के लिए वापस गिर जाएगा), लेकिन उन प्रतियों का प्रदर्शन करेगा जो कॉलर को शायद अनुमान नहीं था।
शून्य का नियम
हम अधिक दुबला इंटरफ़ेस प्राप्त करने के लिए पांच और आरएआई के नियम के सिद्धांतों को जोड़ सकते हैं: शून्य का नियम: किसी भी संसाधन को प्रबंधित करने की आवश्यकता अपने स्वयं के प्रकार में होनी चाहिए। उस प्रकार को पांच के नियम का पालन करना होगा, लेकिन उस संसाधन के सभी उपयोगकर्ताओं को पांच विशेष सदस्य कार्यों में से किसी को लिखने की आवश्यकता नहीं है और बस उन सभी को default
कर सकते हैं।
तीन उदाहरण के नियम में पेश किए गए Person
वर्ग का उपयोग करते हुए, हम cstrings
लिए एक संसाधन-प्रबंधन वस्तु बना सकते हैं:
class cstring {
private:
char* p;
public:
~cstring() { delete [] p; }
cstring(cstring const& );
cstring(cstring&& );
cstring& operator=(cstring const& );
cstring& operator=(cstring&& );
/* other members as appropriate */
};
और एक बार यह अलग हो जाने पर, हमारा Person
वर्ग बहुत सरल हो जाता है:
class Person {
cstring name;
int arg;
public:
~Person() = default;
Person(Person const& ) = default;
Person(Person&& ) = default;
Person& operator=(Person const& ) = default;
Person& operator=(Person&& ) = default;
/* other members as appropriate */
};
Person
में विशेष सदस्यों को स्पष्ट रूप से घोषित करने की आवश्यकता नहीं है; संकलक Person
की सामग्री के आधार पर उन्हें उचित रूप से डिफ़ॉल्ट या हटा देगा। इसलिए, निम्न भी शून्य के नियम का एक उदाहरण है।
struct Person {
cstring name;
int arg;
};
यदि cstring
एक साथ एक चाल-ही प्रकार, होना करने के लिए थे delete
घ प्रतिलिपि निर्माता / असाइनमेंट ऑपरेटर, तो Person
स्वचालित रूप से कदम-केवल रूप में अच्छी तरह हो जाएगा।
शून्य का नियम आर। मार्टिनो फर्नांडीस द्वारा शुरू किया गया था
तीन का नियम
तीन नियमों में कहा गया है कि यदि किसी प्रकार को कभी उपयोगकर्ता द्वारा परिभाषित कॉपी कंस्ट्रक्टर, कॉपी असाइनमेंट ऑपरेटर या डिस्ट्रक्टर की आवश्यकता होती है, तो उसके पास तीनों होने चाहिए।
नियम का कारण यह है कि एक वर्ग को तीन में से किसी एक की आवश्यकता होती है जो कुछ संसाधन (फ़ाइल हैंडल, गतिशील रूप से आवंटित स्मृति, आदि) का प्रबंधन करता है, और तीनों को लगातार उस संसाधन का प्रबंधन करने की आवश्यकता होती है। प्रतिलिपि कार्य सौदा करते हैं कि संसाधन वस्तुओं के बीच कैसे कॉपी किया जाता है, और विध्वंसक RAII सिद्धांतों के अनुसार संसाधन को नष्ट कर देगा।
एक प्रकार पर विचार करें जो एक स्ट्रिंग संसाधन का प्रबंधन करता है:
class Person
{
char* name;
int age;
public:
Person(char const* new_name, int new_age)
: name(new char[std::strlen(new_name) + 1])
, age(new_age)
{
std::strcpy(name, new_name);
}
~Person() {
delete [] name;
}
};
चूंकि name
को कंस्ट्रक्टर में आवंटित किया गया था, इसलिए विध्वंसक इसे लीक होने वाली मेमोरी से बचने के लिए डील करता है। लेकिन अगर ऐसी वस्तु की नकल की जाए तो क्या होगा?
int main()
{
Person p1("foo", 11);
Person p2 = p1;
}
सबसे पहले, p1
का निर्माण किया जाएगा। फिर p1
से p2
कॉपी किया जाएगा। हालाँकि, C ++ - जनरेट किया गया कॉपी कंस्ट्रक्टर टाइप के प्रत्येक कंपोनेंट को कॉपी करेगा जैसा कि है। जिसका मतलब है कि p1.name
और p2.name
दोनों एक ही स्ट्रिंग को इंगित करते हैं।
जब main
समाप्त होता है, तो विनाशकारी कहा जाएगा। पहले p2
के विध्वंसक को कहा जाएगा; यह स्ट्रिंग को हटा देगा। तब p1
का विनाशकर्ता कहा जाएगा। हालाँकि, स्ट्रिंग पहले ही हटा दी गई है । कॉलिंग delete
ऑन मेमोरी जिसे पहले से डिलीट किया गया था अपरिभाषित व्यवहार करता है।
इससे बचने के लिए, एक उपयुक्त कॉपी कंस्ट्रक्टर प्रदान करना आवश्यक है। एक दृष्टिकोण एक संदर्भ गणना प्रणाली को लागू करना है, जहां विभिन्न Person
उदाहरण एक ही स्ट्रिंग डेटा साझा करते हैं। प्रत्येक बार एक कॉपी की जाती है, साझा संदर्भ गणना बढ़ाई जाती है। तब विध्वंसक संदर्भ संख्या को घटाता है, केवल स्मृति को मुक्त करता है यदि गिनती शून्य है।
या हम मूल्य शब्दार्थ और गहरी नकल व्यवहार को लागू कर सकते हैं :
Person(Person const& other)
: name(new char[std::strlen(other.name) + 1])
, age(other.age)
{
std::strcpy(name, other.name);
}
Person &operator=(Person const& other)
{
// Use copy and swap idiom to implement assignment
Person copy(other);
swap(copy); // assume swap() exchanges contents of *this and copy
return *this;
}
एक मौजूदा बफर जारी करने की आवश्यकता से कॉपी असाइनमेंट ऑपरेटर का कार्यान्वयन जटिल है। कॉपी और स्वैप तकनीक एक अस्थायी वस्तु बनाती है जो एक नया बफर रखती है। *this
और copy
की सामग्री की अदला-बदली से मूल बफर की copy
का स्वामित्व मिल जाता copy
। copy
विनाश, जैसे ही फ़ंक्शन वापस आता है, पहले के स्वामित्व वाले बफर को *this
जारी करता है।
स्व-असाइनमेंट सुरक्षा
कॉपी असाइनमेंट ऑपरेटर लिखते समय, यह बहुत महत्वपूर्ण है कि यह स्व-असाइनमेंट की स्थिति में काम करने में सक्षम हो। यही है, इसे इसकी अनुमति देनी होगी:
SomeType t = ...;
t = t;
स्व-असाइनमेंट आमतौर पर ऐसे स्पष्ट तरीके से नहीं होता है। यह आम तौर पर विभिन्न कोड प्रणालियों के माध्यम से एक सर्किट मार्ग के माध्यम से होता है, जहां असाइनमेंट के स्थान में केवल दो Person
बिंदु या संदर्भ होते हैं और उन्हें पता नहीं है कि वे एक ही वस्तु हैं।
आपके द्वारा लिखा गया कोई भी कॉपी असाइनमेंट ऑपरेटर को इसे ध्यान में रखना चाहिए।
ऐसा करने का विशिष्ट तरीका इस तरह की स्थिति में सभी असाइनमेंट लॉजिक को लपेटना है:
SomeType &operator=(const SomeType &other)
{
if(this != &other)
{
//Do assignment logic.
}
return *this;
}
नोट: स्व-असाइनमेंट के बारे में सोचना और यह सुनिश्चित करना महत्वपूर्ण है कि ऐसा होने पर आपका कोड सही व्यवहार करे। हालांकि, स्व-असाइनमेंट एक बहुत ही दुर्लभ घटना है और इसे रोकने के लिए अनुकूलन वास्तव में सामान्य मामले को कम कर सकता है। चूंकि सामान्य मामला बहुत अधिक सामान्य है, इसलिए स्व-असाइनमेंट के लिए निराशा करना आपकी कोड दक्षता को कम कर सकता है (इसलिए इसका उपयोग करने में सावधानी बरतें)।
एक उदाहरण के रूप में, असाइनमेंट ऑपरेटर को लागू करने की सामान्य तकनीक copy and swap idiom
। इस तकनीक का सामान्य कार्यान्वयन स्व-असाइनमेंट के लिए परीक्षण करने के लिए परेशान नहीं करता है (भले ही स्व-असाइनमेंट महंगा है क्योंकि एक प्रतिलिपि बनाई गई है)। कारण यह है कि सामान्य मामले के निराशाकरण को बहुत अधिक महंगा दिखाया गया है (जैसा कि यह अधिक बार होता है)।
हटो असाइनमेंट ऑपरेटरों को स्व-असाइनमेंट के खिलाफ भी संरक्षित किया जाना चाहिए। हालांकि, ऐसे कई ऑपरेटरों के लिए तर्क std::swap
पर आधारित है, जो ठीक से एक ही मेमोरी में / से स्वैपिंग को संभाल सकता है। इसलिए यदि आपके चाल असाइनमेंट तर्क स्वैप ऑपरेशन की एक श्रृंखला से अधिक कुछ नहीं है, तो आपको स्व-असाइनमेंट सुरक्षा की आवश्यकता नहीं है।
अगर ऐसा नहीं है, तो आप इसके बाद के संस्करण के रूप में इसी तरह के कदम उठाने चाहिए।