क्या विध्वंसक कॉलिंग मैन्युअल रूप से हमेशा खराब डिज़ाइन का संकेत है?


84

मैं सोच रहा था: वे कहते हैं कि यदि आप मैन्युअल रूप से विध्वंसक कह रहे हैं - आप कुछ गलत कर रहे हैं। लेकिन क्या हमेशा ऐसा ही होता है? क्या कोई प्रति-उदाहरण हैं? स्थिति जहां इसे मैन्युअल रूप से कॉल करना या जहां से बचने के लिए यह कठिन / असंभव / अव्यवहारिक है, वहां यह करना आवश्यक है।


आप वस्तु को फिर से कॉल किए बिना, डोर को कॉल करने के बाद कैसे डील कर रहे हैं?
ssube

2
@peachykeen: आप प्लेसमेंट newको पुराने के स्थान पर एक नई वस्तु शुरू करने के लिए कहेंगे । आम तौर पर एक अच्छा विचार नहीं है, लेकिन यह अनसुना नहीं है।
21

14
उन "नियमों" को देखें जिनमें "हमेशा" और "कभी नहीं" शब्द शामिल हैं जो सीधे तौर पर विशिष्टताओं से नहीं आते हैं: अधिकांश मामलों में जो उन्हें सिखा रहे हैं वे आपको उन चीजों को छिपाना चाहते हैं जिन्हें आपको जानना चाहिए लेकिन वह नहीं करता है पढ़ाना जानते हैं। जैसे एक वयस्क बच्चे को सेक्स के बारे में एक सवाल का जवाब देता है।
एमिलियो गरवाग्लिया

मुझे लगता है कि प्लेसमेंट टेक्निक के साथ निर्माण के साथ छेड़छाड़ करने के मामले में यह ठीक है। stroustrup.com/bs_faq2.html#placement-delete (लेकिन यह निम्न स्तर की चीज है और केवल तब उपयोग कर रहा है जब आप अपने सॉफ्टवेयर को ऐसे स्तर पर भी अनुकूलित करते हैं)
bruziuz

जवाबों:


95

विध्वंसक को मैन्युअल रूप से कॉल करने की आवश्यकता होती है यदि ऑब्जेक्ट को operator new()" std::nothrow" अधिभार का उपयोग करते हुए छोड़कर, ओवरलोड रूप का उपयोग करके बनाया गया था :

T* t0 = new(std::nothrow) T();
delete t0; // OK: std::nothrow overload

void* buffer = malloc(sizeof(T));
T* t1 = new(buffer) T();
t1->~T(); // required: delete t1 would be wrong
free(buffer);

विनाशकर्ता स्पष्ट रूप से बुला ऊपर के रूप में एक नहीं बल्कि कम स्तर पर बाहर प्रबंध स्मृति, तथापि, है बुरा डिजाइन की निशानी। शायद, यह वास्तव में न सिर्फ बुरा डिजाइन लेकिन एकमुश्त गलत है (हाँ, एक स्पष्ट नाशक असाइनमेंट ऑपरेटर में एक प्रतिलिपि निर्माता कॉल के बाद का उपयोग कर रहा है एक बुरा डिजाइन और संभावित रूप से गलत होने के लिए)।

C ++ 2011 के साथ स्पष्ट विध्वंसक कॉल का उपयोग करने का एक और कारण है: सामान्यीकृत यूनियनों का उपयोग करते समय, वर्तमान ऑब्जेक्ट को स्पष्ट रूप से नष्ट करना और प्रतिनिधित्व ऑब्जेक्ट के प्रकार को बदलते समय प्लेसमेंट नए का उपयोग करके एक नई वस्तु बनाना आवश्यक है। इसके अलावा, जब संघ नष्ट हो जाता है, तो वर्तमान वस्तु के विध्वंसक को स्पष्ट रूप से कॉल करना आवश्यक होता है यदि इसे विनाश की आवश्यकता होती है।


26
"एक अतिभारित रूप का उपयोग करना" कहने के बजाय operator new, सही वाक्यांश "उपयोग करना placement new" है।
रेमी लेबेऊ

5
@RemyLebeau: ठीक है, मैं स्पष्ट करना चाहता था कि मैं केवल operator new(std::size_t, void*)(और सरणी भिन्नता) के बारे में बात नहीं कर रहा हूं , बल्कि सभी अतिभारित संस्करण के बारे में बात कर रहा हूं operator new()
डिटमार कौशल

उस बारे में क्या जब आप किसी ऑब्जेक्ट को कॉपी करने के लिए इसे बदलना चाहते हैं, जबकि ऑपरेशन कंप्यूटिंग के बिना इसे बदलना है? temp = Class(object); temp.operation(); object.~Class(); object = Class(temp); temp.~Class();
जीन-ल्यूक नसिफ कोल्हो

yes, using an explicit destructor followed by a copy constructor call in the assignment operator is a bad design and likely to be wrong। तुम ऐसा क्यों कह रहे हो? मुझे लगता है कि अगर विध्वंसक तुच्छ है, या तुच्छ के करीब है, तो इसका न्यूनतम ओवरहेड है और DRY सिद्धांत का उपयोग बढ़ाता है। यदि इस तरह के मामलों का उपयोग एक चाल के साथ किया जाता है operator=(), तो यह स्वैप का उपयोग करने से भी बेहतर हो सकता है। YMMV।
एड्रियन

1
@ एड्रियन: विध्वंसक को कॉल करना और ऑब्जेक्ट को बहुत आसानी से बदलना ऑब्जेक्ट के प्रकार को बदल देता है: यह ऑब्जेक्ट को स्थिर प्रकार के असाइनमेंट के साथ फिर से बनाएगा लेकिन डायनेमिक प्रकार भिन्न हो सकता है। यह वास्तव में एक मुद्दा है जब कक्षा में virtualफ़ंक्शंस होते हैं ( virtualफ़ंक्शंस दोबारा बनाए नहीं जाएंगे) और अन्यथा ऑब्जेक्ट केवल आंशिक रूप से [पुनः] निर्माण किया जाता है।
डायटमार कुहल

104

सभी उत्तर विशिष्ट मामलों का वर्णन करते हैं, लेकिन एक सामान्य उत्तर है:

आप स्पष्ट रूप से dtor हर बार जब आप बस को नष्ट करने की जरूरत है फोन वस्तु को रिहा बिना (सी ++ अर्थ में) स्मृति में वस्तु बसता था।

यह आमतौर पर सभी स्थिति में होता है जहां मेमोरी आवंटन / डीलक्लोलेशन को ऑब्जेक्ट निर्माण / विनाश से स्वतंत्र रूप से प्रबंधित किया जाता है। उन मामलों में निर्माण प्लेसमेंट के माध्यम से स्मृति के एक अस्तित्व वाले स्थान पर होता है, और विनाश स्पष्ट डोरेट कॉल के माध्यम से होता है।

यहाँ कच्चे उदाहरण है:

{
  char buffer[sizeof(MyClass)];

  {
     MyClass* p = new(buffer)MyClass;
     p->dosomething();
     p->~MyClass();
  }
  {
     MyClass* p = new(buffer)MyClass;
     p->dosomething();
     p->~MyClass();
  }

}

इसके std::allocatorद्वारा उपयोग किए जाने पर एक और उल्लेखनीय उदाहरण डिफ़ॉल्ट है std::vector: तत्वों का निर्माण इसके vectorदौरान किया जाता है push_back, लेकिन मेमोरी को चंक्स में आवंटित किया जाता है, इसलिए यह तत्व निर्देश पूर्व-मौजूद है। और इसलिए, vector::eraseतत्वों को नष्ट करना चाहिए, लेकिन जरूरी नहीं कि यह मेमोरी से निपटता है (विशेषकर यदि नए पुश_बैक को जल्द ही होना है ...)।

यह सख्त ओओपी अर्थों में "खराब डिजाइन" है (आपको वस्तुओं का प्रबंधन करना चाहिए, स्मृति नहीं: तथ्य वस्तुओं को स्मृति की आवश्यकता होती है एक "घटना"), यह "निम्न स्तर की प्रोग्रामिंग" में "अच्छा डिजाइन" है, या उन मामलों में जहां स्मृति है "फ्री स्टोर" से नहीं लिया गया डिफ़ॉल्ट डिफ़ॉल्ट में operator newखरीदता है।

यह खराब डिज़ाइन है यदि यह कोड के आसपास यादृच्छिक रूप से होता है, तो यह अच्छा डिज़ाइन है यदि यह स्थानीय रूप से उस उद्देश्य के लिए डिज़ाइन की गई कक्षाओं के लिए होता है।


8
सिर्फ इस बात के लिए उत्सुक हैं कि यह स्वीकृत उत्तर क्यों नहीं है।
फ्रांसिस कुगलर

12

नहीं, आपको इसे स्पष्ट रूप से नहीं कहना चाहिए क्योंकि इसे दो बार कहा जाएगा। एक बार मैनुअल कॉल के लिए और दूसरी बार जब वह गुंजाइश जिसमें वस्तु घोषित की जाती है समाप्त हो जाती है।

उदाहरण के लिए।

{
  Class c;
  c.~Class();
}

यदि आपको वास्तव में समान संचालन करने की आवश्यकता है तो आपके पास एक अलग विधि होनी चाहिए।

एक विशिष्ट स्थिति है जिसमें आप प्लेसमेंट के साथ गतिशील रूप से आवंटित ऑब्जेक्ट पर एक डिस्ट्रक्टर को कॉल कर सकते हैं, newलेकिन यह कुछ ऐसा नहीं लगता है जिसकी आपको कभी आवश्यकता होगी।


11

नहीं, स्थिति पर निर्भर करता है, कभी-कभी यह वैध और अच्छा डिजाइन होता है।

यह समझने के लिए कि आपको क्यों और कब विध्वंसक रूप से कॉल करने की आवश्यकता है, आइए देखें कि "नया" और "हटाएं" के साथ क्या हो रहा है।

एक वस्तु को गतिशील रूप से बनाने के लिए, T* t = new T;हुड के नीचे: 1. आकार (टी) मेमोरी आवंटित की जाती है। 2. आवंटित मेमोरी को आरंभीकृत करने के लिए T के कंस्ट्रक्टर को कहा जाता है। ऑपरेटर नया दो काम करता है: आवंटन और आरंभीकरण।

delete t;हुड के तहत वस्तु को नष्ट करने के लिए : 1. टी का विनाशकर्ता कहा जाता है। 2. उस ऑब्जेक्ट के लिए आवंटित मेमोरी जारी की जाती है। ऑपरेटर डिलीट भी दो काम करता है: विनाश और डीलक्लोशन।

एक निर्माणकर्ता को आरंभीकरण करने के लिए लिखता है, और विनाश करने के लिए विध्वंसक। जब आप स्पष्ट रूप से विध्वंसक को बुलाते हैं, तो केवल विनाश होता है, लेकिन निपटारा नहीं

स्पष्ट रूप से विध्वंसक कॉलिंग का एक वैध उपयोग, इसलिए, हो सकता है "मैं केवल ऑब्जेक्ट को नष्ट करना चाहता हूं, लेकिन मैं मेमोरी आवंटन (अभी तक) जारी नहीं करता (या नहीं कर सकता)।"

इसका एक सामान्य उदाहरण, कुछ वस्तुओं के पूल के लिए पूर्व-आवंटित मेमोरी है, जिसे अन्यथा गतिशील रूप से आवंटित किया जाना है।

एक नई वस्तु बनाते समय, आप पूर्व-आवंटित पूल से मेमोरी का हिस्सा प्राप्त करते हैं और "प्लेसमेंट नया" करते हैं। ऑब्जेक्ट के साथ किए जाने के बाद, आप सफाई कार्य को समाप्त करने के लिए विध्वंसक को स्पष्ट रूप से कॉल करना चाह सकते हैं, यदि कोई हो। लेकिन आप वास्तव में मेमोरी को नहीं हटाएंगे, क्योंकि ऑपरेटर डिलीट कर चुका होगा। इसके बजाय, आप पुन: उपयोग के लिए पूल में चंक को लौटाते हैं।


9

एफएक्यू द्वारा उद्धृत के रूप में, आपको प्लेसमेंट नए का उपयोग करते समय विध्वंसक को स्पष्ट रूप से कॉल करना चाहिए

यह केवल उस समय के बारे में है जब आप कभी स्पष्ट रूप से एक विध्वंसक कहते हैं।

मैं मानता हूँ कि यह शायद ही कभी जरूरत है।


6

किसी भी समय आपको आवंटन से आवंटन को अलग करने की आवश्यकता होती है, आपको मैन्युअल रूप से विध्वंसक के नए और स्पष्ट कॉलिंग की आवश्यकता होगी। आज, यह शायद ही कभी आवश्यक है, क्योंकि हमारे पास मानक कंटेनर हैं, लेकिन अगर आपको कुछ नए प्रकार के कंटेनर को लागू करना है, तो आपको इसकी आवश्यकता होगी।


3

ऐसे मामले हैं जब वे आवश्यक हैं:

कोड में मैं काम करता हूं मैं आवंटन में स्पष्ट विध्वंसक कॉल का उपयोग करता हूं, मेरे पास सरल आवंटनकर्ता का कार्यान्वयन है जो स्टोटर कंटेनरों में मेमोरी ब्लॉकों को वापस करने के लिए प्लेसमेंट नए का उपयोग करता है। मुझे नष्ट करने में:

  void destroy (pointer p) {
    // destroy objects by calling their destructor
    p->~T();
  }

निर्माण के समय:

  void construct (pointer p, const T& value) {
    // initialize memory with placement new
    #undef new
    ::new((PVOID)p) T(value);
  }

प्लेटफ़ॉर्म विशिष्ट आवंटन और डीलोकॉक तंत्र का उपयोग करते हुए, डिक्लॉकेट () में आवंटन () और मेमोरी डीलोकेशन में आवंटन भी किया जा रहा है। यह एलोकेटर डॉग लेग मॉलोक को बायपास करने के लिए उपयोग किया गया था और उदाहरण के लिए विंडोज़ पर लोकलऑलोक के लिए सीधे उपयोग किया गया था।


1

मुझे 3 मौके मिले जहाँ मुझे ऐसा करने की ज़रूरत थी:

  • मेमोरी-मैप्ड-io या साझा मेमोरी द्वारा बनाई गई मेमोरी में ऑब्जेक्ट्स को आवंटित / डील करना
  • जब C ++ का उपयोग करके किसी दिए गए C इंटरफ़ेस को लागू किया जाता है (हाँ यह आज भी दुर्भाग्य से होता है (क्योंकि मेरे पास इसे बदलने के लिए पर्याप्त सुराग नहीं है))
  • जब आवंटन वर्गों को लागू करना

1

मैं कभी भी ऐसी स्थिति में नहीं आया हूं जहां किसी को विध्वंसक को मैन्युअल रूप से कॉल करने की आवश्यकता हो। मुझे लगता है कि स्ट्रॉन्स्ट्रुप का दावा है कि यह बुरा व्यवहार है।


1
तुम सही हो। लेकिन मैंने एक नए प्लेसमेंट का उपयोग किया है। मैं सफाई विधि को अन्य विधि में जोड़ने में सक्षम था फिर विध्वंसक। विध्वंसक वहाँ है तो यह "स्वचालित रूप से" कहा जा सकता है जब कोई हटाता है, जब आप मैन्युअल रूप से विनाश करना चाहते हैं, लेकिन सौदा नहीं करते हैं तो आप बस एक "onDestruct" लिख सकते हैं क्या आप नहीं कर सकते? मुझे यह सुनने में दिलचस्पी होगी कि क्या ऐसे उदाहरण हैं जहां किसी वस्तु को विध्वंसक में अपना विनाश करना होगा क्योंकि कभी-कभी आपको हटाने की आवश्यकता होगी और अन्य बार जब आप केवल विनाश करना चाहते हैं और
निपटा

और उस मामले में भी आप विध्वंसक के भीतर से onDestruct () कॉल कर सकते हैं - इसलिए मैं अभी भी मैन्युअल रूप से विध्वंसक को कॉल करने के लिए कोई मामला नहीं देखता हूं।
लेउवे

4
@JimBalter: के निर्माता C+
मार्क के कोवान

@ मर्कोवन: सी + क्या है? यह C ++
डिस्ट्रक्टोरर

1

इस बारे में क्या?
विध्वंसक को नहीं कहा जाता है यदि निर्माणकर्ता से एक अपवाद फेंका जाता है, तो मुझे इसे मैन्युअल रूप से उन हैंडल्स को नष्ट करने के लिए कॉल करना होगा जो अपवाद से पहले निर्माणकर्ता में बनाए गए हैं।

class MyClass {
  HANDLE h1,h2;
  public:
  MyClass() {
    // handles have to be created first
    h1=SomeAPIToCreateA();
    h2=SomeAPIToCreateB();        
    try {
      ...
      if(error) {
        throw MyException();
      }
    }
    catch(...) {
      this->~MyClass();
      throw;
    }
  }
  ~MyClass() {
    SomeAPIToDestroyA(h1);
    SomeAPIToDestroyB(h2);
  }
};

1
यह संदेहास्पद लगता है: जब आपका कंस्ट्रक्टर चखता है, तो आपको पता नहीं है (या पता नहीं हो सकता है) कि ऑब्जेक्ट के किन हिस्सों का निर्माण किया गया है और जो नहीं किया गया था। इसलिए आप उदाहरण के लिए, विध्वंसक को कॉल करने के लिए किन उप-वस्तुओं को नहीं जानते हैं। या कंस्ट्रक्टर द्वारा आवंटित संसाधनों में से कौन सा सौदा करना है।
वायलेट जिराफ

@VioletGiraffe यदि उप-वस्तुओं का निर्माण स्टैक पर किया जाता है, अर्थात "नए" के साथ नहीं, तो वे स्वचालित रूप से नष्ट हो जाएंगे। अन्यथा आप जांच सकते हैं कि क्या वे विनाशकारी में उन्हें नष्ट करने से पहले NULL हैं। संसाधनों के साथ ही
CITBL

जिस तरह से आपने ctorयहां लिखा है वह गलत है, बिल्कुल उसी कारण से जब आपने खुद को प्रदान किया था: यदि संसाधन आवंटन विफल रहता है, तो सफाई के साथ समस्या है। एक 'ctor' को कॉल नहीं करना चाहिए this->~dtor()निर्मित वस्तुओं dtorपर बुलाया जाना चाहिए , और इस मामले में वस्तु का निर्माण अभी तक नहीं किया गया है। जो भी हो, सफाई को संभालना चाहिए। कोड के अंदर , आपको ऐसे मामलों में उपयोग करना चाहिए, जब कुछ फेंकता है, तो मामलों में आपके लिए स्वचालित सफाई को संभालना चाहिए । स्वचालित सफाई का समर्थन करने के लिए कक्षा में फ़ील्ड बदलना एक अच्छा विचार हो सकता है। ctorctorstd::unique_ptrHANDLE h1, h2
quetzalcoatl

इसका मतलब है, कि ctor को ऐसा दिखना चाहिए: MyClass(){ cleanupGuard1<HANDLE> tmp_h1(&SomeAPIToDestroyA) = SomeAPIToCreateA(); cleanupGuard2<HANDLE> tmp_h2(&SomeAPIToDestroyB) = SomeAPIToCreateB(); if(error) { throw MyException(); } this->h1 = tmp_h1.release(); this->h2 = tmp_h2.release(); }और यह बात है । कोई भी जोखिम भरा मैनुअल सफाई नहीं, आंशिक रूप से निर्मित वस्तु में कोई भंडारण नहीं करता है जब तक कि सब कुछ सुरक्षित न हो, बोनस के रूप में आता है। यदि आप HANDLE h1,h2कक्षा में बदल जाते हैं cleanupGuard<HANDLE> h1;आदि, तो आपको इसकी आवश्यकता भी नहीं हो सकती है dtor
quetzalcoatl

का कार्यान्वयन cleanupGuard1और इस cleanupGuard2बात पर निर्भर करता है कि एक प्रासंगिक xxxToCreateरिटर्न क्या है और एक प्रासंगिक xxxxToDestroyटेक क्या पैरामीटर लेता है। यदि वे सरल हैं, तो आपको कुछ भी लिखने की आवश्यकता नहीं हो सकती है, क्योंकि यह अक्सर पता चलता है std::unique_ptr<x,deleter()>(या एक समान) दोनों मामलों में आपके लिए चाल कर सकता है।
quetzalcoatl

-2

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

struct Variant {
    union {
        std::string str;
        int num;
        bool b;
    };
    enum Type { Str, Int, Bool } type;
};

यदि Variantउदाहरण एक धारण कर रहा था std::string, और अब आप संघ को एक अलग प्रकार प्रदान कर रहे हैं, तो आपको std::stringपहले को नष्ट करना होगा । कंपाइलर स्वचालित रूप से ऐसा नहीं करेगा


-4

मेरे पास एक और स्थिति है जहां मुझे लगता है कि विध्वंसक को कॉल करना पूरी तरह से उचित है।

ऑब्जेक्ट को अपनी प्रारंभिक स्थिति को पुनर्स्थापित करने के लिए "रीसेट" प्रकार की विधि लिखते समय, रीसेट किए जा रहे पुराने डेटा को हटाने के लिए डेस्ट्रक्टर को कॉल करना पूरी तरह से उचित है।

class Widget
{
private: 
    char* pDataText { NULL  }; 
    int   idNumber  { 0     };

public:
    void Setup() { pDataText = new char[100]; }
    ~Widget()    { delete pDataText;          }

    void Reset()
    {
        Widget blankWidget;
        this->~Widget();     // Manually delete the current object using the dtor
        *this = blankObject; // Copy a blank object to the this-object.
    }
};

1
यह क्लीनर नहीं लगेगा आप एक विशेष घोषित करता है, तो cleanup()विधि इस मामले में कहा जा और नाशक में?
वायलेट जिराफ

एक "विशेष" विधि जिसे केवल दो मामलों में कहा जाता है? ज़रूर ... जो पूरी तरह से सही लगता है (/ व्यंग्य)। तरीकों को सामान्यीकृत किया जाना चाहिए और कहीं भी बुलाया जा सकता है। जब आप किसी ऑब्जेक्ट को हटाना चाहते हैं, तो इसके विध्वंसक को कॉल करने में कुछ भी गलत नहीं है।
अबेलेंकी

4
आपको इस स्थिति में विध्वंसक को स्पष्ट रूप से कॉल नहीं करना चाहिए। आपको वैसे भी असाइनमेंट ऑपरेटर को लागू करना होगा।
रेमी
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.