C++
विशेष सदस्य के कार्य
खोज…
आभासी और संरक्षित विध्वंसक
एक वर्ग जिसे विरासत में दिया गया है, को बेस क्लास कहा जाता है। ऐसी कक्षा के विशेष सदस्य कार्यों के साथ देखभाल की जानी चाहिए।
रन-टाइम (बेस क्लास के लिए एक पॉइंटर के माध्यम से) को पॉलीमॉर्फिक रूप से उपयोग किए जाने वाले वर्ग को विनाशकारी virtual
घोषित करना चाहिए। यह ऑब्जेक्ट के व्युत्पन्न हिस्सों को ठीक से नष्ट करने की अनुमति देता है, तब भी जब ऑब्जेक्ट बेस क्लास के लिए एक पॉइंटर के माध्यम से नष्ट हो जाता है।
class Base {
public:
virtual ~Base() = default;
private:
// data members etc.
};
class Derived : public Base { // models Is-A relationship
public:
// some methods
private:
// more data members
};
// virtual destructor in Base ensures that derived destructors
// are also called when the object is destroyed
std::unique_ptr<Base> base = std::make_unique<Derived>();
base = nullptr; // safe, doesn't leak Derived's members
यदि वर्ग को बहुरूपी होने की आवश्यकता नहीं है, लेकिन फिर भी इसके इंटरफ़ेस को विरासत में प्राप्त करने की अनुमति देने की आवश्यकता है, तो गैर-आभासी protected
विध्वंसक का उपयोग करें।
class NonPolymorphicBase {
public:
// some methods
protected:
~NonPolymorphicBase() = default; // note: non-virtual
private:
// etc.
};
इस तरह के एक वर्ग को एक सूचक के माध्यम से कभी भी नष्ट नहीं किया जा सकता है, स्लाइसिंग के कारण मूक रिसाव से बचा जा सकता है।
यह तकनीक विशेष रूप से private
बेस कक्षाओं के लिए डिज़ाइन की गई कक्षाओं पर लागू होती है। अनुकूलन बिंदुओं के रूप में virtual
तरीके प्रदान करते हुए इस तरह के वर्ग का उपयोग कुछ सामान्य कार्यान्वयन विवरणों को संलग्न करने के लिए किया जा सकता है। इस तरह के वर्ग को कभी भी बहुरूपिक रूप से उपयोग नहीं किया जाना चाहिए, और एक protected
विध्वंसक इस आवश्यकता को सीधे कोड में दर्ज करने में मदद करता है।
अंत में, कुछ वर्गों को आवश्यकता हो सकती है कि उनका उपयोग आधार वर्ग के रूप में कभी न किया जाए। इस स्थिति में, वर्ग को final
रूप से चिह्नित किया जा सकता है। एक सामान्य गैर-आभासी सार्वजनिक विध्वंसक इस मामले में ठीक है।
class FinalClass final { // marked final here
public:
~FinalClass() = default;
private:
// etc.
};
लागू करें और प्रतिलिपि बनाएँ
इस बात को ध्यान में रखें कि विध्वंसक घोषित करने से संकलक को अंतर्निहित चाल निर्माणकर्ताओं को स्थानांतरित करने और असाइनमेंट ऑपरेटरों को स्थानांतरित करने से रोकता है। यदि आप एक विध्वंसक घोषित करते हैं, तो याद रखें कि चाल संचालन के लिए उपयुक्त परिभाषाएं भी जोड़ें।
इसके अलावा, चाल के संचालन की घोषणा कॉपी संचालन की पीढ़ी को दबा देगी, इसलिए इन्हें भी जोड़ा जाना चाहिए (यदि इस वर्ग की वस्तुओं को कॉपी शब्दार्थ की आवश्यकता है)।
class Movable {
public:
virtual ~Movable() noexcept = default;
// compiler won't generate these unless we tell it to
// because we declared a destructor
Movable(Movable&&) noexcept = default;
Movable& operator=(Movable&&) noexcept = default;
// declaring move operations will suppress generation
// of copy operations unless we explicitly re-enable them
Movable(const Movable&) = default;
Movable& operator=(const Movable&) = default;
};
कॉपी और स्वैप करें
यदि आप एक वर्ग लिख रहे हैं जो संसाधनों का प्रबंधन करता है, तो आपको सभी विशेष सदस्य कार्यों को लागू करने की आवश्यकता है ( तीन / पांच / शून्य का नियम देखें)। कॉपी कंस्ट्रक्टर और असाइनमेंट ऑपरेटर को लिखने का सबसे सीधा तरीका होगा:
person(const person &other)
: name(new char[std::strlen(other.name) + 1])
, age(other.age)
{
std::strcpy(name, other.name);
}
person& operator=(person const& rhs) {
if (this != &other) {
delete [] name;
name = new char[std::strlen(other.name) + 1];
std::strcpy(name, other.name);
age = other.age;
}
return *this;
}
लेकिन इस दृष्टिकोण में कुछ समस्याएं हैं। यह मजबूत अपवाद गारंटी विफल रहता है - अगर new[]
फेंकता है, हम पहले से ही के स्वामित्व वाले संसाधनों साफ़ कर दिया है this
और वापस नहीं ले पाता। हम कॉपी असाइनमेंट में कॉपी कंस्ट्रक्शन के बहुत सारे तर्क दोहरा रहे हैं। और हमें स्व-असाइनमेंट चेक को याद रखना होगा, जो आमतौर पर कॉपी ऑपरेशन में ओवरहेड जोड़ता है, लेकिन अभी भी महत्वपूर्ण है।
मजबूत अपवाद गारंटी को पूरा करने और कोड दोहराव से बचने के लिए (बाद के कदम असाइनमेंट ऑपरेटर के साथ ऐसा दोहरा), हम कॉपी-और-स्वैप मुहावरे का उपयोग कर सकते हैं:
class person {
char* name;
int age;
public:
/* all the other functions ... */
friend void swap(person& lhs, person& rhs) {
using std::swap; // enable ADL
swap(lhs.name, rhs.name);
swap(lhs.age, rhs.age);
}
person& operator=(person rhs) {
swap(*this, rhs);
return *this;
}
};
यह काम क्यों करता है? गौर कीजिए कि जब हमारे पास क्या होता है
person p1 = ...;
person p2 = ...;
p1 = p2;
सबसे पहले, हम कॉपी-निर्माण rhs
से p2
(जो हम यहाँ नकल करने नहीं था)। यदि वह ऑपरेशन फेंकता है, तो हम operator=
में कुछ भी नहीं करते हैं operator=
और p1
अछूता रहता है। अगला, हम सदस्यों को *this
और rhs
बीच स्वैप करते हैं, और फिर rhs
दायरे से बाहर हो जाते हैं। जब operator=
, कि परोक्ष की मूल संसाधनों साफ this
(नाशक है, जो हम नकल नहीं था के माध्यम से)। स्व-असाइनमेंट भी काम करता है - यह कॉपी-एंड-स्वैप (एक अतिरिक्त आवंटन और डील्लोकेशन शामिल है) के साथ कम कुशल है, लेकिन अगर यह संभावना नहीं है, तो हम इसके उपयोग के लिए विशिष्ट उपयोग के मामले को धीमा नहीं करते हैं।
उपरोक्त सूत्रीकरण कार्य के लिए पहले से ही है।
p1 = std::move(p2);
यहां, हम p2
से rhs
-निर्माण निर्माण करते हैं, और बाकी सभी वैध हैं। यदि कोई वर्ग चल सकने योग्य है, लेकिन प्रतिलिपि योग्य नहीं है, तो कॉपी-असाइनमेंट को हटाने की कोई आवश्यकता नहीं है, क्योंकि यह असाइनमेंट ऑपरेटर केवल हटाए गए कॉपी कंस्ट्रक्टर के कारण बीमार हो जाएगा।
डिफ़ॉल्ट निर्माता
डिफॉल्ट कंस्ट्रक्टर एक प्रकार का कंस्ट्रक्टर होता है जिसे कॉल करने पर कोई पैरामीटर नहीं चाहिए। इसका नाम उस प्रकार के नाम पर रखा गया है जो इसका निर्माण करता है और इसका सदस्य कार्य है (जैसा कि सभी निर्माणकर्ता हैं)।
class C{
int i;
public:
// the default constructor definition
C()
: i(0){ // member initializer list -- initialize i to 0
// constructor function body -- can do more complex things here
}
};
C c1; // calls default constructor of C to create object c1
C c2 = C(); // calls default constructor explicitly
C c3(); // ERROR: this intuitive version is not possible due to "most vexing parse"
C c4{}; // but in C++11 {} CAN be used in a similar way
C c5[2]; // calls default constructor for both array elements
C* c6 = new C[2]; // calls default constructor for both array elements
डेवलपर को सभी मापदंडों के लिए डिफ़ॉल्ट मान प्रदान करने के लिए "कोई मापदंडों" की आवश्यकता को पूरा करने का एक और तरीका है:
class D{
int i;
int j;
public:
// also a default constructor (can be called with no parameters)
D( int i = 0, int j = 42 )
: i(i), j(j){
}
};
D d; // calls constructor of D with the provided default values for the parameters
कुछ परिस्थितियों में (अर्थात, डेवलपर कोई निर्माता प्रदान नहीं करता है और कोई अन्य अयोग्य स्थिति नहीं है), संकलक निहित रूप से एक खाली डिफ़ॉल्ट निर्माता प्रदान करता है:
class C{
std::string s; // note: members need to be default constructible themselves
};
C c1; // will succeed -- C has an implicitly defined default constructor
कुछ अन्य प्रकार के निर्माणकर्ता पहले बताई गई अयोग्य स्थितियों में से एक हैं:
class C{
int i;
public:
C( int i ) : i(i){}
};
C c1; // Compile ERROR: C has no (implicitly defined) default constructor
अंतर्निहित डिफ़ॉल्ट निर्माणकर्ता को रोकने के लिए, एक सामान्य तकनीक इसे private
(बिना किसी परिभाषा के) घोषित करने के लिए है। इरादा एक संकलित त्रुटि का कारण बनता है जब कोई कंस्ट्रक्टर का उपयोग करने की कोशिश करता है (यह या तो कंपाइलर के आधार पर एक्सेस में निजी त्रुटि या एक लिंकर त्रुटि का परिणाम है)।
यह सुनिश्चित करने के लिए कि एक डिफ़ॉल्ट निर्माता (कार्यात्मक रूप से निहित एक के समान) को परिभाषित किया गया है, एक डेवलपर खाली एक स्पष्ट रूप से लिख सकता है।
C ++ 11 में, एक कंपाइलर को डिफ़ॉल्ट कंस्ट्रक्टर प्रदान करने से रोकने के लिए एक डेवलपर delete
कीवर्ड का भी उपयोग कर सकता है।
class C{
int i;
public:
// default constructor is explicitly deleted
C() = delete;
};
C c1; // Compile ERROR: C has its default constructor deleted
इसके अलावा, एक डेवलपर को डिफ़ॉल्ट निर्माता प्रदान करने के लिए संकलक को वांछित करने के बारे में भी स्पष्ट हो सकता है।
class C{
int i;
public:
// does have automatically generated default constructor (same as implicit one)
C() = default;
C( int i ) : i(i){}
};
C c1; // default constructed
C c2( 1 ); // constructed with the int taking constructor
आप यह निर्धारित कर सकते हैं कि क्या std::is_default_constructible
का उपयोग करके किसी प्रकार का डिफ़ॉल्ट कंस्ट्रक्टर (या कोई आदिम प्रकार है) std::is_default_constructible
<type_traits>
:
class C1{ };
class C2{ public: C2(){} };
class C3{ public: C3(int){} };
using std::cout; using std::boolalpha; using std::endl;
using std::is_default_constructible;
cout << boolalpha << is_default_constructible<int>() << endl; // prints true
cout << boolalpha << is_default_constructible<C1>() << endl; // prints true
cout << boolalpha << is_default_constructible<C2>() << endl; // prints true
cout << boolalpha << is_default_constructible<C3>() << endl; // prints false
C ++ 11 में, अभी भी std::is_default_constructible
के गैर- std::is_default_constructible
संस्करण का उपयोग करना संभव है std::is_default_constructible
:
cout << boolalpha << is_default_constructible<C1>::value << endl; // prints true
नाशक
एक विध्वंसक तर्कों के बिना एक फ़ंक्शन है जिसे तब कहा जाता है जब उपयोगकर्ता द्वारा परिभाषित ऑब्जेक्ट नष्ट होने वाला होता है। यह उस प्रकार के नाम पर है जिसे यह एक ~
उपसर्ग के साथ नष्ट कर देता है।
class C{
int* is;
string s;
public:
C()
: is( new int[10] ){
}
~C(){ // destructor definition
delete[] is;
}
};
class C_child : public C{
string s_ch;
public:
C_child(){}
~C_child(){} // child destructor
};
void f(){
C c1; // calls default constructor
C c2[2]; // calls default constructor for both elements
C* c3 = new C[2]; // calls default constructor for both array elements
C_child c_ch; // when destructed calls destructor of s_ch and of C base (and in turn s)
delete[] c3; // calls destructors on c3[0] and c3[1]
} // automatic variables are destroyed here -- i.e. c1, c2 and c_ch
अधिकांश परिस्थितियों में (यानी, कोई उपयोगकर्ता कोई विध्वंसक प्रदान नहीं करता है, और कोई अन्य अयोग्य स्थिति नहीं है), संकलक एक डिफ़ॉल्ट विध्वंसक प्रदान करता है:
class C{
int i;
string s;
};
void f(){
C* c1 = new C;
delete c1; // C has a destructor
}
class C{
int m;
private:
~C(){} // not public destructor!
};
class C_container{
C c;
};
void f(){
C_container* c_cont = new C_container;
delete c_cont; // Compile ERROR: C has no accessible destructor
}
C ++ 11 में, एक डेवलपर कंपाइलर को डिफ़ॉल्ट डिस्ट्रक्टर प्रदान करने से रोककर इस व्यवहार को ओवरराइड कर सकता है।
class C{
int m;
public:
~C() = delete; // does NOT have implicit destructor
};
void f{
C c1;
} // Compile ERROR: C has no destructor
इसके अलावा, एक डेवलपर को डिफ़ॉल्ट डिस्ट्रक्टर प्रदान करने के लिए कंपाइलर के बारे में भी स्पष्ट होना चाहिए।
class C{
int m;
public:
~C() = default; // saying explicitly it does have implicit/empty destructor
};
void f(){
C c1;
} // C has a destructor -- c1 properly destroyed
आप यह निर्धारित कर सकते हैं कि क्या std::is_destructible
का उपयोग करके एक प्रकार का एक विध्वंसक (या एक आदिम प्रकार है) std::is_destructible
<type_traits>
:
class C1{ };
class C2{ public: ~C2() = delete };
class C3 : public C2{ };
using std::cout; using std::boolalpha; using std::endl;
using std::is_destructible;
cout << boolalpha << is_destructible<int>() << endl; // prints true
cout << boolalpha << is_destructible<C1>() << endl; // prints true
cout << boolalpha << is_destructible<C2>() << endl; // prints false
cout << boolalpha << is_destructible<C3>() << endl; // prints false