C ++ विध्वंसक कब कहा जाता है?


118

बेसिक प्रश्न: जब कोई प्रोग्राम C ++ में क्लास को 'विध्वंसक विधि कहता है? मुझे बताया गया है कि यह तब कहा जाता है जब भी कोई वस्तु किसी दायरे से बाहर जाती है या ए के अधीन होती हैdelete

अधिक विशिष्ट प्रश्न:

1) यदि ऑब्जेक्ट एक पॉइंटर के माध्यम से बनाया गया है और उस पॉइंटर को बाद में डिलीट कर दिया गया है या इंगित करने के लिए एक नया पता दिया गया है, तो क्या वह ऑब्जेक्ट अपने डिस्ट्रक्टर को कॉल करने के लिए इशारा कर रहा है (यह मानते हुए कि और कुछ नहीं इशारा कर रहा है)?

2) प्रश्न 1 के बाद, क्या परिभाषित करता है जब कोई वस्तु दायरे से बाहर जाती है (जब कोई वस्तु किसी दिए गए {ब्लॉक} को छोड़ती है)। तो, दूसरे शब्दों में, एक लिंक किए गए सूची में किसी ऑब्जेक्ट पर विध्वंसक कब कहा जाता है?

3) क्या आप कभी एक विध्वंसक को मैन्युअल रूप से कॉल करना चाहेंगे?


3
यहां तक ​​कि आपके विशिष्ट प्रश्न भी व्यापक हैं। "उस सूचक को बाद में हटा दिया गया है" और "इंगित करने के लिए एक नया पता दिया गया" काफी अलग हैं। और खोज करें (इनमें से कुछ का उत्तर दिया गया है), और फिर उन हिस्सों के लिए अलग-अलग प्रश्न पूछें जिन्हें आप नहीं ढूंढ सकते।
मैथ्यू फ्लैशेन

जवाबों:


74

1) यदि ऑब्जेक्ट एक पॉइंटर के माध्यम से बनाया गया है और उस पॉइंटर को बाद में डिलीट कर दिया गया है या इंगित करने के लिए एक नया पता दिया गया है, तो क्या वह ऑब्जेक्ट अपने डिस्ट्रक्टर को कॉल करने के लिए इशारा कर रहा है (यह मानते हुए कि और कुछ नहीं इशारा कर रहा है)?

यह संकेत के प्रकार पर निर्भर करता है। उदाहरण के लिए, स्मार्ट पॉइंटर्स अक्सर डिलीट होने पर अपनी वस्तुओं को हटा देते हैं। साधारण संकेत नहीं करते हैं। जब कोई संकेतक किसी भिन्न ऑब्जेक्ट को इंगित करने के लिए बनाया जाता है, तो यह सच है। कुछ स्मार्ट पॉइंटर्स पुरानी वस्तु को नष्ट कर देंगे, या इसे नष्ट कर देंगे यदि इसके कोई और संदर्भ नहीं हैं। साधारण बिंदुओं में ऐसे स्मार्ट नहीं होते हैं। वे बस एक पते को पकड़ते हैं और आपको उन वस्तुओं पर संचालन करने की अनुमति देते हैं जो वे विशेष रूप से ऐसा करने के लिए इंगित करते हैं।

2) प्रश्न 1 के बाद, क्या परिभाषित करता है जब कोई वस्तु दायरे से बाहर जाती है (जब कोई वस्तु किसी दिए गए {ब्लॉक} को छोड़ती है)। तो, दूसरे शब्दों में, एक लिंक किए गए सूची में किसी ऑब्जेक्ट पर विध्वंसक कब कहा जाता है?

यह लिंक की गई सूची के कार्यान्वयन तक है। विशिष्ट संग्रह नष्ट होने पर उनकी सभी निहित वस्तुओं को नष्ट कर देते हैं।

तो, पॉइंटर्स की एक लिंक्ड सूची आमतौर पर पॉइंटर्स को नष्ट कर देती है, लेकिन उन वस्तुओं को नहीं जो वे इंगित करते हैं। (जो सही हो सकता है। वे अन्य बिंदुओं द्वारा संदर्भ हो सकते हैं।) विशेष रूप से संकेत करने के लिए डिज़ाइन की गई एक लिंक सूची, हालांकि, अपने स्वयं के विनाश पर वस्तुओं को हटा सकती है।

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

3) क्या आप कभी एक विध्वंसक को मैन्युअल रूप से कॉल करना चाहेंगे?

ज़रूर। एक उदाहरण यह होगा कि यदि आप किसी वस्तु को उसी प्रकार की दूसरी वस्तु से बदलना चाहते हैं, लेकिन स्मृति को फिर से आवंटित करने के लिए मुक्त नहीं करना चाहते हैं। आप जगह में पुरानी वस्तु को नष्ट कर सकते हैं और जगह में एक नया निर्माण कर सकते हैं। (हालांकि, आमतौर पर यह एक बुरा विचार है।)

// pointer is destroyed because it goes out of scope,
// but not the object it pointed to. memory leak
if (1) {
 Foo *myfoo = new Foo("foo");
}


// pointer is destroyed because it goes out of scope,
// object it points to is deleted. no memory leak
if(1) {
 Foo *myfoo = new Foo("foo");
 delete myfoo;
}

// no memory leak, object goes out of scope
if(1) {
 Foo myfoo("foo");
}

2
मुझे लगा कि आपके उदाहरणों में से अंतिम ने एक फ़ंक्शन घोषित किया है? यह "सबसे डरावने तोते" का एक उदाहरण है। (अन्य अधिक तुच्छ बिंदु यह है कि मुझे लगता है कि आप new Foo()एक राजधानी 'एफ' के साथ हैं।)
स्टुअर्ट गोलोडेट

1
मुझे लगता Foo myfoo("foo")है कि मोस्ट वैक्सिंग पार्स नहीं है, लेकिन char * foo = "foo"; Foo myfoo(foo);है।
कोसाइन

यह एक बेवकूफी भरा सवाल हो सकता है, लेकिन क्या delete myFooइसे पहले नहीं कहा जाना चाहिए Foo *myFoo = new Foo("foo");? या फिर आप नई बनाई गई वस्तु को हटा देंगे, नहीं?
मथियस रोचा

लाइन myFooसे पहले कोई नहीं है Foo *myFoo = new Foo("foo");। वह लाइन myFooकिसी भी मौजूदा नाम को छायांकित करते हुए एक बिल्कुल नया वैरिएबल बनाती है । हालांकि इस मामले में, मौजूदा के बाद से कोई भी मौजूद नहीं myFooहै if, जो समाप्त हो गया है।
डेविड श्वार्ट्ज

1
@galactikuh एक "स्मार्ट पॉइंटर" एक ऐसी चीज है, जो किसी ऑब्जेक्ट के लिए पॉइंटर की तरह काम करता है, लेकिन इसमें ऐसी विशेषताएं भी होती हैं, जो उस ऑब्जेक्ट के जीवनकाल को प्रबंधित करना आसान बनाते हैं।
डेविड श्वार्ट्ज

20

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

इसका जवाब है हाँ। @DavidSchwartz ने एक उदाहरण दिया, लेकिन यह काफी असामान्य है। मैं एक उदाहरण देता हूँ जो कि C ++ प्रोग्रामर के बहुत सारे समय के उपयोग के घेरे में है: std::vector(और std::deque, हालाँकि इसका उपयोग बहुत अधिक नहीं किया गया है)।

जैसा कि ज्यादातर लोग जानते हैं, std::vectorस्मृति का एक बड़ा ब्लॉक आवंटित करेगा जब / यदि आप अपने वर्तमान आवंटन से अधिक आइटम जोड़ सकते हैं। जब यह ऐसा करता है, हालांकि, इसमें मेमोरी का एक ब्लॉक होता है जो वर्तमान में वेक्टर की तुलना में अधिक वस्तुओं को रखने में सक्षम है ।

यह प्रबंधित करने के लिए कि vectorकवर के तहत क्या होता है , ऑब्जेक्ट के माध्यम से कच्ची मेमोरी आवंटित की जाती Allocatorहै (जो, जब तक आप अन्यथा निर्दिष्ट नहीं करते हैं, इसका मतलब है कि यह उपयोग करता है ::operator new)। फिर, जब आप push_backकिसी आइटम को जोड़ने के लिए (उदाहरण के लिए) उपयोग करते हैं vector, तो आंतरिक रूप से वेक्टर placement newअपनी मेमोरी स्पेस के अप्रयुक्त भाग (पहले) में एक आइटम बनाने के लिए उपयोग करता है।

अब, क्या होगा जब / यदि आप eraseवेक्टर से एक आइटम? यह सिर्फ उपयोग नहीं कर सकता है delete- जो इसकी मेमोरी के पूरे ब्लॉक को रिलीज़ करेगा; किसी भी अन्य को नष्ट किए बिना उस मेमोरी में एक वस्तु को नष्ट करने की आवश्यकता होती है, या किसी भी मेमोरी को ब्लॉक करने से मुक्त करता है जिसे वह नियंत्रित करता है (उदाहरण के लिए, यदि आप eraseएक वेक्टर से 5 आइटम, तो तुरंत push_back5 और आइटम, यह गारंटी है कि वेक्टर पुनः लोड नहीं करेगा स्मृति जब आप ऐसा करते हैं।

ऐसा करने के लिए, वेक्टर सीधे ऑब्जेक्ट को स्पष्ट रूप से विध्वंसक कहकर स्मृति में नष्ट कर देता है, उपयोग नहीं करके delete

हैं, तो हो सकता है, किसी और को एक कंटेनर सन्निहित भंडारण मोटे तौर पर की तरह एक का उपयोग कर लिखने के लिए थे vectorकरता है (या कि किसी भिन्न रूप, की तरह std::dequeवास्तव में करता है), तो आप लगभग निश्चित रूप से उसी तकनीक का उपयोग करना चाहते हैं।

उदाहरण के लिए, आइए विचार करें कि आप एक परिपत्र रिंग-बफर के लिए कोड कैसे लिख सकते हैं।

#ifndef CBUFFER_H_INC
#define CBUFFER_H_INC

template <class T>
class circular_buffer {
    T *data;
    unsigned read_pos;
    unsigned write_pos;
    unsigned in_use;
    const unsigned capacity;
public:
    circular_buffer(unsigned size) :
        data((T *)operator new(size * sizeof(T))),
        read_pos(0),
        write_pos(0),
        in_use(0),
        capacity(size)
    {}

    void push(T const &t) {
        // ensure there's room in buffer:
        if (in_use == capacity) 
            pop();

        // construct copy of object in-place into buffer
        new(&data[write_pos++]) T(t);
        // keep pointer in bounds.
        write_pos %= capacity;
        ++in_use;
    }

    // return oldest object in queue:
    T front() {
        return data[read_pos];
    }

    // remove oldest object from queue:
    void pop() { 
        // destroy the object:
        data[read_pos++].~T();

        // keep pointer in bounds.
        read_pos %= capacity;
        --in_use;
    }
  
~circular_buffer() {
    // first destroy any content
    while (in_use != 0)
        pop();

    // then release the buffer.
    operator delete(data); 
}

};

#endif

मानक कंटेनरों के विपरीत, यह उपयोग करता है operator newऔर operator deleteसीधे। वास्तविक उपयोग के लिए, आप शायद एक एलोकेटर वर्ग का उपयोग करना चाहते हैं, लेकिन फिलहाल यह योगदान (आईएमओ, वैसे भी) की तुलना में विचलित करने के लिए अधिक होगा।


9
  1. जब आप एक ऑब्जेक्ट बनाते newहैं, तो आप कॉल करने के लिए जिम्मेदार होते हैं delete। जब आप किसी ऑब्जेक्ट को बनाते हैं make_shared, तो परिणाम shared_ptrगिनती और कॉल के लिए ज़िम्मेदार होता है deleteजब उपयोग की संख्या शून्य हो जाती है।
  2. दायरे से बाहर जाने का मतलब ब्लॉक छोड़ना है। यह तब होता है जब विध्वंसक कहा जाता है, यह मानते हुए कि ऑब्जेक्ट को आवंटित नहीं किया गया था new(यानी यह एक स्टैक ऑब्जेक्ट है)।
  3. केवल उस समय के बारे में जब आपको विध्वंसक को कॉल करने की आवश्यकता होती है, जब आप ऑब्जेक्ट को प्लेसमेंट केnew साथ आवंटित करते हैं

1
वहाँ संदर्भ गिनती (साझा_प्ट्र) है, हालांकि स्पष्ट रूप से सादे बिंदुओं के लिए नहीं।
पबबी

1
@ पब: अच्छी बात है, चलो अच्छा अभ्यास को बढ़ावा दें। संपादित उत्तर।
MSalters

6

1) वस्तुओं को 'पॉइंटर्स के माध्यम से' नहीं बनाया जाता है। एक पॉइंटर है जो किसी भी ऑब्जेक्ट को आपको 'नया' में सौंपा गया है। यदि आप प्‍वाइंटर पर 'डिलीट' कहते हैं, तो इसका अर्थ यह है कि यह पॉइंटर डेरेफेरेंस ऑब्जेक्ट को डिलीट करेगा (और डिस्ट्रक्टर को कॉल करेगा)। यदि आप पॉइंटर को किसी अन्य ऑब्जेक्ट पर असाइन करते हैं, तो मेमोरी रिसाव होगा; C ++ में कुछ भी आपके लिए आपका कचरा एकत्र नहीं करेगा।

2) ये दो अलग-अलग प्रश्न हैं। जब स्टैक फ्रेम इसे में घोषित किया जाता है, तो एक स्कोप दायरे से बाहर हो जाता है। आमतौर पर यह तब होता है जब आप एक ब्लॉक छोड़ देते हैं। ढेर में वस्तुएँ कभी भी दायरे से बाहर नहीं जाती हैं, हालांकि स्टैक पर उनके संकेत हो सकते हैं। विशेष गारंटी में कुछ भी नहीं है कि किसी लिंक की गई सूची में किसी वस्तु का विनाशकर्ता कहा जाएगा।

3) वास्तव में नहीं। ऐसा डीप मैजिक हो सकता है जो अन्यथा सुझाव देगा, लेकिन आम तौर पर आप अपने with नए ’कीवर्ड को अपने 'डिलीट’ कीवर्ड के साथ मैच करना चाहते हैं, और अपने विध्वंसक में अपना सबकुछ डालना चाहते हैं, यह सुनिश्चित करने के लिए कि यह अपने आप ठीक हो जाए। यदि आप ऐसा नहीं करते हैं, तो किसी भी वर्ग को उपयोग करने वाले विशिष्ट निर्देशों के साथ विध्वंसक पर टिप्पणी करना सुनिश्चित करें कि उन्हें उस ऑब्जेक्ट के संसाधनों को मैन्युअल रूप से कैसे साफ करना चाहिए।


3

प्रश्न 3 का एक विस्तृत उत्तर देने के लिए: हाँ, ऐसे (दुर्लभ) अवसर होते हैं जब आप विध्वंसक को स्पष्ट रूप से कॉल कर सकते हैं, विशेष रूप से एक प्लेसमेंट नए के समकक्ष के रूप में, जैसा कि दासब्लिंकलाइटलाइट देखता है।

इसका एक ठोस उदाहरण देने के लिए:

#include <iostream>
#include <new>

struct Foo
{
    Foo(int i_) : i(i_) {}
    int i;
};

int main()
{
    // Allocate a chunk of memory large enough to hold 5 Foo objects.
    int n = 5;
    char *chunk = static_cast<char*>(::operator new(sizeof(Foo) * n));

    // Use placement new to construct Foo instances at the right places in the chunk.
    for(int i=0; i<n; ++i)
    {
        new (chunk + i*sizeof(Foo)) Foo(i);
    }

    // Output the contents of each Foo instance and use an explicit destructor call to destroy it.
    for(int i=0; i<n; ++i)
    {
        Foo *foo = reinterpret_cast<Foo*>(chunk + i*sizeof(Foo));
        std::cout << foo->i << '\n';
        foo->~Foo();
    }

    // Deallocate the original chunk of memory.
    ::operator delete(chunk);

    return 0;
}

इस प्रकार का उद्देश्य वस्तु निर्माण से स्मृति आवंटन को कम करना है।


2
  1. संकेत - नियमित संकेत RAII का समर्थन नहीं करते हैं। एक स्पष्ट के बिना delete, कचरा होगा। सौभाग्य से C ++ में ऑटो पॉइंटर्स हैं जो आपके लिए इसे संभालते हैं!

  2. स्कोप - सोचें कि जब एक चर आपके प्रोग्राम के लिए अदृश्य हो जाता है । {block}जैसा कि आप बताते हैं, आमतौर पर यह अंत में होता है।

  3. मैनुअल विनाश - यह कभी प्रयास न करें। बस गुंजाइश और आरएआई आप के लिए जादू करते हैं।


एक नोट: आपके लिंक का उल्लेख करते हुए, auto_ptr को हटा दिया गया है।
tnecniv

std::auto_ptrC ++ 11 में पदावनत है, हाँ। यदि ओपी के पास वास्तव में C ++ 11 है, तो उसे std::unique_ptrएकल मालिकों के लिए, या std::shared_ptrसंदर्भ-गिने गए कई मालिकों के लिए उपयोग करना चाहिए ।
क्रिसकॉक

'मैनुअल विनाश - कभी भी यह प्रयास न करें'। मैं अक्सर एक सिस्टम कॉल का उपयोग करके एक अलग थ्रेड पर ऑब्जेक्ट पॉइंटर्स को कतारबद्ध करता हूं जो कंपाइलर को समझ में नहीं आता है। स्कोप / ऑटो / स्मार्ट पॉइंटर्स पर रैलिंग ’मेरे एप्स को भयावह रूप से विफल करने का कारण बनेगी क्योंकि उपभोक्ता थ्रेड को कॉलिंग थ्रेड द्वारा हटाए जाने से पहले उन्हें उपभोक्ता थ्रेड द्वारा नियंत्रित किया जा सकता है। यह समस्या स्कोप-सीमित और परिष्कृत वस्तुओं और इंटरफेस को प्रभावित करती है। केवल पॉइंटर्स और स्पष्ट डिलीट करेंगे।
मार्टिन जेम्स

@MartinJames क्या आप एक सिस्टम कॉल का एक उदाहरण पोस्ट कर सकते हैं जो संकलक को समझ में नहीं आता है? और आप कतार को कैसे लागू कर रहे हैं? नहीं, std::queue<std::shared_ptr>?मैंने पाया है कि pipe()एक निर्माता और उपभोक्ता के बीच कंसिस्टेंसी बहुत आसान बना देता है, अगर नकल बहुत महंगी नहीं है।
चिरसायकॉक

myObject = new myClass (); PostMessage (aHandle, WM_APP, 0, LPPARAM (myObject));
मार्टिन जेम्स

1

जब भी आप "नया" का उपयोग करते हैं, अर्थात, एक पॉइंटर के लिए एक पता संलग्न करें, या यह कहें कि आप ढेर पर जगह का दावा करते हैं, तो आपको इसे "हटाने" की आवश्यकता है।
1. हाँ, जब आप कुछ हटाते हैं, तो विध्वंसक को कहा जाता है।
2. जब लिंक की गई सूची के विध्वंसक को कहा जाता है, तो यह वस्तुओं का विनाशकर्ता कहलाता है। लेकिन अगर वे संकेत हैं, तो आपको उन्हें मैन्युअल रूप से हटाने की आवश्यकता है। 3. जब अंतरिक्ष "नया" द्वारा दावा किया जाता है।


0

हां, एक डिस्ट्रक्टर (उर्फ डोरट) को तब कहा जाता है जब कोई ऑब्जेक्ट स्कोप से बाहर निकलता है या जब आप deleteपॉइंटर पर किसी ऑब्जेक्ट पर कॉल करते हैं ।

  1. यदि पॉइंटर के जरिए डिलीट deleteकिया जाता है तो डोरट को कॉल किया जाएगा। यदि आप deleteपहले कॉल किए बिना पॉइंटर को फिर से असाइन करते हैं , तो आपको मेमोरी लीक मिलेगा क्योंकि ऑब्जेक्ट अभी भी मेमोरी में कहीं मौजूद है। बाद के उदाहरण में, डॉटर को नहीं कहा जाता है।

  2. एक अच्छा जुड़ा हुआ सूची कार्यान्वयन सूची में सभी वस्तुओं के डोर को बुलाएगा जब सूची नष्ट हो रही है (क्योंकि आपने इसे नष्ट करने के लिए कुछ विधि को बुलाया था या यह स्वयं ही दायरे से बाहर हो गया था)। यह कार्यान्वयन पर निर्भर है।

  3. मुझे संदेह है, लेकिन मुझे आश्चर्य नहीं होगा अगर वहाँ कुछ विषम परिस्थिति हो।


1
"यदि आप पहले डिलीट किए बिना पॉइंटर को फिर से असाइन करते हैं, तो आपको एक मेमोरी लीक मिलेगी क्योंकि ऑब्जेक्ट अभी भी मेमोरी में कहीं मौजूद है।" जरुरी नहीं। इसे दूसरे पॉइंटर के जरिए डिलीट किया जा सकता था।
मैथ्यू फ्लेशेन

0

यदि ऑब्जेक्ट एक पॉइंटर के माध्यम से नहीं बनाया जाता है (उदाहरण के लिए, ए 1 = ए ();), ऑब्जेक्ट को तबाह होने पर डिस्ट्रक्टर कहा जाता है, हमेशा जब फ़ंक्शन जहां ऑब्जेक्ट निहित है समाप्त हो जाता है। उदाहरण के लिए:

void func()
{
...
A a1 = A();
...
}//finish


जब कोड "फिनिश" को निष्पादित किया जाता है, तो विध्वंसक को कहा जाता है।

यदि ऑब्जेक्ट एक पॉइंटर के माध्यम से बनाया गया है (उदाहरण के लिए, A * a2 = new A (;), तो पॉइंटर को कहा जाता है जब पॉइंटर को डिलीट किया जाता है (a2 को डिलीट करें;)। यदि पॉइंट को यूजर्स द्वारा डिलीट नहीं किया जाता है, तो एक्सप्लोसिव या दिया जाता है। इसे हटाने से पहले नया पता, स्मृति रिसाव को ठीक किया जाता है। यह एक बग है।

एक लिंक की गई सूची में, यदि हम std :: list <> का उपयोग करते हैं, तो हमें desctructor या मेमोरी लीक की परवाह नहीं करनी चाहिए क्योंकि std :: list <> ने हमारे लिए इन सभी को समाप्त कर दिया है। स्वयं द्वारा लिखी गई एक लिंक की गई सूची में, हमें डिसट्रक्टर्स लिखना चाहिए और पॉइंटर को आसानी से हटा देना चाहिए। इसके अलावा, यह मेमोरी लीक का कारण होगा।

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

मेरी खराब अंग्रेजी के लिए माफी चाहूंगा!


यह सच नहीं है कि आप एक विध्वंसक को मैन्युअल रूप से कॉल नहीं कर सकते - आप कर सकते हैं (उदाहरण के लिए, मेरे उत्तर में कोड देखें)। यह सच है कि आपके पास
जितना

0

याद रखें कि किसी ऑब्जेक्ट का कंस्ट्रक्टर उस ऑब्जेक्ट के लिए मेमोरी आवंटित होने के तुरंत बाद कहा जाता है और जबकि डिस्ट्रक्टर को उस ऑब्जेक्ट की मेमोरी को डील करने से ठीक पहले कहा जाता है।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.