c++ - सी++ में ऑब्जेक्ट विनाश
exception destructor (2)
ऑब्जेक्ट जीवनकाल समाप्त होता है और यह नष्ट हो जाता है जब किसी ऑब्जेक्ट का विनाशक स्वचालित रूप से कॉल किया जाता है। आपको आमतौर पर इसे मैन्युअल रूप से कॉल नहीं करना चाहिए।
हम इस वस्तु का एक उदाहरण के रूप में उपयोग करेंगे:
class Test
{
public:
Test() { std::cout << "Created " << this << "\n";}
~Test() { std::cout << "Destroyed " << this << "\n";}
Test(Test const& rhs) { std::cout << "Copied " << this << "\n";}
Test& operator=(Test const& rhs) { std::cout << "Assigned " << this << "\n";}
};
सी ++ में तीन (सी ++ 11 में चार) ऑब्जेक्ट के विशिष्ट प्रकार हैं और ऑब्जेक्ट का प्रकार ऑब्जेक्ट्स लाइफेंस को परिभाषित करता है।
- स्टेटिक स्टोरेज अवधि ऑब्जेक्ट्स
- स्वचालित भंडारण अवधि वस्तुओं
- गतिशील भंडारण अवधि वस्तुओं
- (सी ++ 11 में) थ्रेड स्टोरेज अवधि ऑब्जेक्ट्स
स्टेटिक स्टोरेज अवधि ऑब्जेक्ट्स
ये वैश्विक चर के लिए सबसे सरल और समान हैं। इन वस्तुओं का जीवनकाल (आमतौर पर) आवेदन की लंबाई है। मुख्य रूप से बाहर निकलने के बाद ये मुख्य रूप से दर्ज किए जाते हैं और नष्ट हो जाते हैं (बनाए जाने के विपरीत क्रम में)।
Test global;
int main()
{
std::cout << "Main\n";
}
> ./a.out
Created 0x10fbb80b0
Main
Destroyed 0x10fbb80b0
नोट 1: दो अन्य प्रकार की स्थिर भंडारण अवधि वस्तु है।
एक वर्ग के स्थिर सदस्य चर।
ये सभी अर्थों और उद्देश्यों के लिए जीवनकाल के संदर्भ में वैश्विक चर के समान हैं।
एक समारोह के अंदर स्थिर चर।
ये आलसी रूप से स्थिर भंडारण अवधि वस्तुओं बनाते हैं। वे पहले उपयोग पर बनाए जाते हैं (सी ++ 11 के लिए थ्रेड सुरक्षित मैनेजर में)। अन्य स्थिर स्टोरेज अवधि ऑब्जेक्ट्स की तरह ही जब एप्लिकेशन समाप्त होता है तो वे नष्ट हो जाते हैं।
निर्माण / विनाश का आदेश
- एक संकलन इकाई के भीतर निर्माण का आदेश अच्छी तरह से परिभाषित किया गया है और घोषणा के समान ही है।
- संकलन इकाइयों के बीच निर्माण का आदेश अपरिभाषित है।
- विनाश का आदेश निर्माण के आदेश के ठीक विपरीत है।
स्वचालित भंडारण अवधि वस्तुओं
ये सबसे आम प्रकार की वस्तुएं हैं और आपको 99% समय का उपयोग करना चाहिए।
ये स्वचालित चर के तीन मुख्य प्रकार हैं:
- एक समारोह / ब्लॉक के अंदर स्थानीय चर
- वर्ग / सरणी के अंदर सदस्य चर।
- अस्थायी चर
स्थानीय चर
जब कोई फ़ंक्शन / ब्लॉक निकल जाता है तो उस फ़ंक्शन / ब्लॉक के अंदर घोषित सभी चर नष्ट हो जाएंगे (सृजन के विपरीत क्रम में)।
int main()
{
std::cout << "Main() START\n";
Test scope1;
Test scope2;
std::cout << "Main Variables Created\n";
{
std::cout << "\nblock 1 Entered\n";
Test blockScope;
std::cout << "block 1 about to leave\n";
} // blockScope is destrpyed here
{
std::cout << "\nblock 2 Entered\n";
Test blockScope;
std::cout << "block 2 about to leave\n";
} // blockScope is destrpyed here
std::cout << "\nMain() END\n";
}// All variables from main destroyed here.
> ./a.out
Main() START
Created 0x7fff6488d938
Created 0x7fff6488d930
Main Variables Created
block 1 Entered
Created 0x7fff6488d928
block 1 about to leave
Destroyed 0x7fff6488d928
block 2 Entered
Created 0x7fff6488d918
block 2 about to leave
Destroyed 0x7fff6488d918
Main() END
Destroyed 0x7fff6488d930
Destroyed 0x7fff6488d938
सदस्य चर
एक सदस्य चर के जीवनकाल उस वस्तु से बंधे हैं जो इसका मालिक है। जब एक मालिक जीवनकाल समाप्त होता है तो उसके सभी सदस्यों का जीवनकाल समाप्त होता है। इसलिए आपको एक ऐसे मालिक के जीवनकाल को देखने की ज़रूरत है जो समान नियमों का पालन करता हो।
नोट: मालिकों को सृजन के विपरीत क्रम में मालिक के समक्ष हमेशा नष्ट कर दिया जाता है।
- इस प्रकार कक्षा के सदस्यों के लिए वे घोषणा के क्रम में बनाए जाते हैं
और घोषणा के विपरीत क्रम में नष्ट कर दिया - इस प्रकार सरणी सदस्यों के लिए वे 0 -> शीर्ष क्रम में बनाए जाते हैं
और रिवर्स ऑर्डर टॉप में नष्ट हो गया -> 0
अस्थायी चर
ये ऑब्जेक्ट्स हैं जो अभिव्यक्ति के परिणामस्वरूप बनाई गई हैं लेकिन वेरिएबल को असाइन नहीं की गई हैं। अस्थायी चर अन्य स्वचालित चर की तरह नष्ट हो जाते हैं। यह सिर्फ इतना है कि उनके दायरे का अंत बयान का अंत है जिसमें वे बनाए जाते हैं (यह हमला है ';')।
std::string data("Text.");
std::cout << (data + 1); // Here we create a temporary object.
// Which is a std::string with '1' added to "Text."
// This object is streamed to the output
// Once the statement has finished it is destroyed.
// So the temporary no longer exists after the ';'
नोट: ऐसी स्थितियां हैं जहां अस्थायी जीवन को बढ़ाया जा सकता है।
लेकिन यह इस सरल चर्चा के लिए प्रासंगिक नहीं है। जब तक आप समझते हैं कि यह दस्तावेज़ आपके लिए दूसरी प्रकृति होगी और इससे पहले कि यह अस्थायी जीवन का विस्तार कर रहा हो, वह ऐसा कुछ नहीं है जिसे आप करना चाहते हैं।
गतिशील भंडारण अवधि वस्तुओं
इन वस्तुओं में एक गतिशील जीवन है और इसे delete
लिए कॉल के साथ new
और नष्ट कर दिया गया है।
int main()
{
std::cout << "Main()\n";
Test* ptr = new Test();
delete ptr;
std::cout << "Main Done\n";
}
> ./a.out
Main()
Created 0x1083008e0
Destroyed 0x1083008e0
Main Done
कचरे से आने वाले देवताओं के लिए भाषा एकत्रित होती है, यह अजीब लग सकती है (आपके ऑब्जेक्ट के जीवनकाल का प्रबंधन)। लेकिन समस्या ऐसा लगता है जितना बुरा लगता है। गतिशील रूप से आवंटित वस्तुओं का उपयोग करने के लिए सी ++ में यह असामान्य है। हमारे पास जीवनकाल को नियंत्रित करने के लिए प्रबंधन वस्तुएं हैं।
अधिकांश जीसी एकत्रित भाषाओं की सबसे नज़दीकी चीज std::shared_ptr
। यह गतिशील रूप से बनाए गए ऑब्जेक्ट के उपयोगकर्ताओं की संख्या का ट्रैक रखेगा और जब वे सभी चले गए हैं तो स्वचालित रूप से delete
जाएंगे (मैं इसे सामान्य जावा ऑब्जेक्ट के बेहतर संस्करण के रूप में सोचता हूं)।
int main()
{
std::cout << "Main Start\n";
std::shared_ptr<Test> smartPtr(new Test());
std::cout << "Main End\n";
} // smartPtr goes out of scope here.
// As there are no other copies it will automatically call delete on the object
// it is holding.
> ./a.out
Main Start
Created 0x1083008e0
Main Ended
Destroyed 0x1083008e0
थ्रेड संग्रहण अवधि वस्तुओं
ये भाषा के लिए नए हैं। वे स्थिर भंडारण अवधि वस्तुओं की तरह बहुत अधिक हैं। लेकिन उसी जीवन को उसी एप्लिकेशन के रूप में रहने के बजाए जब तक वे निष्पादन के धागे से जुड़े होते हैं, तब तक वे रहते हैं।
जब सी ++ में वस्तुओं को नष्ट कर दिया जाता है, और इसका क्या अर्थ है? क्या मुझे कचरा कलेक्टर नहीं है, क्योंकि मुझे उन्हें मैन्युअल रूप से नष्ट करना है? अपवाद कैसे खेलते हैं?
(नोट: यह स्टैक ओवरफ्लो के सी ++ एफएक्यू में प्रवेश करने के लिए है । यदि आप इस फॉर्म में एक एफएक्यू प्रदान करने के विचार की आलोचना करना चाहते हैं, तो मेटा पर पोस्ट करना जो यह सब शुरू कर देगा, ऐसा करने का स्थान होगा। उस प्रश्न की निगरानी सी ++ चैटरूम में की जाती है, जहां एफएक्यू विचार पहली जगह शुरू हुआ था, इसलिए आपके उत्तर को उन लोगों द्वारा पढ़ा जाने की संभावना है जो इस विचार के साथ आए थे।)
निम्नलिखित पाठ में, मैं स्कोप्ड ऑब्जेक्ट्स के बीच अंतर करूंगा, जिसका विनाश का समय उनके संलग्न क्षेत्र (कार्य, ब्लॉक, कक्षाएं, अभिव्यक्ति), और गतिशील वस्तुओं द्वारा निर्धारित रूप से निर्धारित किया जाता है, जिसका विनाश का सटीक समय आमतौर पर रनटाइम तक ज्ञात नहीं होता है।
जबकि वर्ग वस्तुओं के विनाश अर्थशास्त्र विनाशकों द्वारा निर्धारित किए जाते हैं, स्केलर ऑब्जेक्ट का विनाश हमेशा एक नो-ऑप होता है। विशेष रूप से, एक सूचक चर को नष्ट करना पॉइंट को नष्ट नहीं करता है।
स्कॉप्ड ऑब्जेक्ट्स
स्वचालित वस्तुओं
स्वचालित ऑब्जेक्ट्स (जिसे आमतौर पर "स्थानीय चर" के रूप में जाना जाता है) को उनकी परिभाषा के विपरीत क्रम में नष्ट कर दिया जाता है, जब नियंत्रण प्रवाह उनकी परिभाषा के दायरे को छोड़ देता है:
void some_function()
{
Foo a;
Foo b;
if (some_condition)
{
Foo y;
Foo z;
} <--- z and y are destructed here
} <--- b and a are destructed here
यदि किसी फ़ंक्शन के निष्पादन के दौरान कोई अपवाद फेंक दिया जाता है, तो पहले से निर्मित स्वचालित ऑब्जेक्ट्स को कॉलर को अपवाद के पहले नष्ट कर दिया जाता है। इस प्रक्रिया को ढेर अवांछित कहा जाता है। अवांछित ढेर के दौरान, कोई और अपवाद उपर्युक्त पूर्व निर्मित स्वचालित वस्तुओं के विनाशकों को छोड़ सकता है। अन्यथा, कार्य std::terminate
कहा जाता है।
यह सी ++ में सबसे महत्वपूर्ण दिशानिर्देशों में से एक की ओर जाता है:
विनाशकों को कभी फेंकना नहीं चाहिए।
गैर-स्थानीय स्थिर वस्तुओं
नामस्थान स्कोप (आमतौर पर "ग्लोबल वेरिएबल" के रूप में जाना जाता है) पर परिभाषित स्टेटिक ऑब्जेक्ट्स और स्थिर डेटा सदस्यों को main
से निष्पादन के बाद, उनकी परिभाषा के विपरीत क्रम में, नष्ट कर दिया जाता है:
struct X
{
static Foo x; // this is only a *declaration*, not a *definition*
};
Foo a;
Foo b;
int main()
{
} <--- y, x, b and a are destructed here
Foo X::x; // this is the respective definition
Foo y;
ध्यान दें कि विभिन्न अनुवाद इकाइयों में परिभाषित स्थैतिक वस्तुओं के निर्माण (और विनाश) का सापेक्ष आदेश अनिर्धारित है।
यदि कोई अपवाद एक स्थैतिक वस्तु के विनाशक को छोड़ देता है, तो कार्य std::terminate
कहा जाता है।
स्थानीय स्थिर वस्तुओं
कार्यों के अंदर परिभाषित स्थिर वस्तुएं तब बनाई जाती हैं जब (और यदि) प्रवाह प्रवाह पहली बार उनकी परिभाषा के माध्यम से गुजरता है। 1 main
के निष्पादन के बाद वे विपरीत क्रम में नष्ट हो जाते हैं:
Foo& get_some_Foo()
{
static Foo x;
return x;
}
Bar& get_some_Bar()
{
static Bar y;
return y;
}
int main()
{
get_some_Bar().do_something(); // note that get_some_Bar is called *first*
get_some_Foo().do_something();
} <--- x and y are destructed here // hence y is destructed *last*
यदि कोई अपवाद एक स्थैतिक वस्तु के विनाशक को छोड़ देता है, तो कार्य std::terminate
कहा जाता है।
1: यह एक बेहद सरलीकृत मॉडल है। स्थैतिक वस्तुओं का प्रारंभिक विवरण वास्तव में अधिक जटिल है।
बेस क्लास सबोबजेक्ट्स और सदस्य सबोबजेक्ट्स
जब नियंत्रण प्रवाह किसी ऑब्जेक्ट के विनाशक निकाय को छोड़ देता है, तो इसके सदस्य उपनिवेश (जिसे "डेटा सदस्य" भी कहा जाता है) उनकी परिभाषा के विपरीत क्रम में नष्ट हो जाते हैं। उसके बाद, बेस-विनिर्देश-सूची के विपरीत क्रम में इसकी बेस क्लास सबोबजेक्ट्स को नष्ट कर दिया जाता है:
class Foo : Bar, Baz
{
Quux x;
Quux y;
public:
~Foo()
{
} <--- y and x are destructed here,
}; followed by the Baz and Bar base class subobjects
यदि Foo
के उप-प्रोजेक्ट्स में से एक के निर्माण के दौरान अपवाद फेंक दिया जाता है, तो अपवाद प्रचार होने से पहले इसके सभी पहले से निर्मित सबोबजेक्ट्स को नष्ट कर दिया जाएगा। दूसरी तरफ, Foo
विनाशक को निष्पादित नहीं किया जाएगा, क्योंकि Foo
ऑब्जेक्ट का पूरी तरह से निर्माण नहीं हुआ था।
ध्यान दें कि विनाशक निकाय डेटा सदस्यों को खुद को नष्ट करने के लिए ज़िम्मेदार नहीं है। यदि कोई डेटा सदस्य किसी संसाधन के लिए एक हैंडल है जिसे ऑब्जेक्ट नष्ट कर दिया जाता है (जैसे फ़ाइल, सॉकेट, डेटाबेस कनेक्शन, म्यूटेक्स, या हीप मेमोरी) को रिलीज़ करने की आवश्यकता होती है तो आपको केवल एक विनाशक लिखना होगा।
सरणी तत्व
अवरोही तत्व अवरोही क्रम में नष्ट कर रहे हैं। यदि एन-वें तत्व के निर्माण के दौरान कोई अपवाद फेंक दिया जाता है, तो अपवाद प्रसारित होने से पहले एन-1 से 0 तत्वों को नष्ट कर दिया जाता है।
अस्थायी वस्तुओं
एक अस्थायी वस्तु का निर्माण तब किया जाता है जब कक्षा प्रकार की एक प्रचलित अभिव्यक्ति का मूल्यांकन किया जाता है। एक प्रक्षेपण अभिव्यक्ति का सबसे प्रमुख उदाहरण एक ऐसे कार्य का आह्वान है जो किसी ऑब्जेक्ट को मूल्य द्वारा लौटाता है, जैसे T operator+(const T&, const T&)
। सामान्य परिस्थितियों में, अस्थायी वस्तु को तब नष्ट कर दिया जाता है जब पूरी अभिव्यक्ति में पूर्ण अभिव्यक्ति का पूर्ण मूल्यांकन किया जाता है:
__________________________ full-expression
___________ subexpression
_______ subexpression
some_function(a + " " + b);
^ both temporary objects are destructed here
उपर्युक्त फ़ंक्शन some_function(a + " " + b)
पूर्ण अभिव्यक्ति है क्योंकि यह एक बड़ी अभिव्यक्ति का हिस्सा नहीं है (इसके बजाय, यह अभिव्यक्ति-कथन का हिस्सा है)। इसलिए, उप-अभिव्यक्तियों के मूल्यांकन के दौरान बनाए गए सभी अस्थायी वस्तुओं को अर्धविराम पर नष्ट कर दिया जाएगा। ऐसी दो अस्थायी वस्तुएं हैं: पहला पहला जोड़ के दौरान बनाया गया है, और दूसरा दूसरा जोड़ा के दौरान बनाया गया है। दूसरी अस्थायी वस्तु पहले से पहले नष्ट हो जाएगी।
यदि दूसरे जोड़े के दौरान कोई अपवाद फेंक दिया जाता है, तो अपवाद को प्रसारित करने से पहले पहली अस्थायी वस्तु ठीक से नष्ट हो जाएगी।
यदि एक स्थानीय संदर्भ एक प्रवीण अभिव्यक्ति के साथ शुरू किया गया है, तो अस्थायी वस्तु का जीवनकाल स्थानीय संदर्भ के दायरे तक बढ़ाया जाता है, इसलिए आपको एक खतरनाक संदर्भ नहीं मिलेगा:
{
const Foo& r = a + " " + b;
^ first temporary (a + " ") is destructed here
// ...
} <--- second temporary (a + " " + b) is destructed not until here
यदि गैर-वर्ग प्रकार की एक प्रचलित अभिव्यक्ति का मूल्यांकन किया जाता है, तो परिणाम एक मान है , अस्थायी वस्तु नहीं। हालांकि, अगर किसी संदर्भ को प्रारंभ करने के लिए प्रयुक्त का उपयोग किया जाता है तो एक अस्थायी वस्तु का निर्माण किया जाएगा :
const int& r = i + j;
गतिशील वस्तुओं और सरणी
निम्नलिखित खंड में, एक्स को नष्ट करने का अर्थ है "पहले एक्स को नष्ट करें और फिर अंतर्निहित स्मृति को छोड़ दें"। इसी तरह, एक्स का अर्थ है "पहले पर्याप्त मेमोरी आवंटित करें और फिर वहां एक्स बनाएं "।
गतिशील वस्तुओं
p = new Foo
के माध्यम से बनाई गई गतिशील वस्तु को delete p
माध्यम से नष्ट कर दिया जाता है। यदि आप delete p
को delete p
भूल जाते हैं, तो आपके पास संसाधन रिसाव है। आपको कभी भी निम्न में से कोई एक करने का प्रयास नहीं करना चाहिए, क्योंकि वे सभी अपरिभाषित व्यवहार का कारण बनते हैं:
-
delete[]
(स्क्वायर ब्रैकेट्स को नोट करें),free
या किसी अन्य माध्यम के माध्यम से एक गतिशील वस्तु कोdelete[]
free
- एक गतिशील वस्तु को कई बार नष्ट करें
- नष्ट होने के बाद एक गतिशील वस्तु का उपयोग करें
यदि एक गतिशील वस्तु के निर्माण के दौरान एक अपवाद फेंक दिया जाता है, तो अपवाद प्रसारित होने से पहले अंतर्निहित स्मृति जारी की जाती है। (विनाशक स्मृति रिलीज से पहले निष्पादित नहीं किया जाएगा, क्योंकि वस्तु पूरी तरह से कभी नहीं बनाया गया था।)
गतिशील सरणी
p = new Foo[n]
के माध्यम से बनाई गई एक गतिशील सरणी delete[] p
माध्यम से नष्ट हो जाती है (स्क्वायर ब्रैकेट्स को नोट करें)। यदि आप delete[] p
को delete[] p
भूल जाते हैं, तो आपके पास संसाधन रिसाव है। आपको कभी भी निम्न में से कोई एक करने का प्रयास नहीं करना चाहिए, क्योंकि वे सभी अपरिभाषित व्यवहार का कारण बनते हैं:
-
delete
,free
या किसी अन्य माध्यम के माध्यम से एक गतिशील सरणी कोdelete
free
- कई बार एक गतिशील सरणी को नष्ट करें
- नष्ट होने के बाद एक गतिशील सरणी का उपयोग करें
यदि एन-वें तत्व के निर्माण के दौरान कोई अपवाद फेंक दिया जाता है, तो तत्वों को एन -1 से 0 अवरोही क्रम में नष्ट कर दिया जाता है, अंतर्निहित स्मृति जारी की जाती है, और अपवाद प्रसारित होता है।
(आपको आमतौर पर गतिशील सरणी के लिए std::vector<Foo>
Foo*
पर पसंद करना चाहिए। यह सही और मजबूत कोड लिखना बहुत आसान बनाता है।)
संदर्भ-गिनती स्मार्ट पॉइंटर्स
कई std::shared_ptr<Foo>
ऑब्जेक्ट्स द्वारा प्रबंधित गतिशील ऑब्जेक्ट को अंतिम std::shared_ptr<Foo>
उस गतिशील ऑब्जेक्ट को साझा करने में शामिल ऑब्जेक्ट के विनाश के दौरान नष्ट हो जाता है।
(आपको आमतौर पर साझा वस्तुओं के लिए std::shared_ptr<Foo>
Foo*
पर पसंद करना चाहिए। यह सही और मजबूत कोड लिखना बहुत आसान बनाता है।)