क्या एक बड़ी सूची को नष्ट करने से मेरा ढेर खत्म हो जाएगा?


12

निम्नलिखित सिंगली लिंक्ड सूची कार्यान्वयन पर विचार करें:

struct node {
    std::unique_ptr<node> next;
    ComplicatedDestructorClass data;
}

अब, मान लीजिए कि मैं कुछ std::unique_ptr<node> headऐसे उदाहरणों का उपयोग करना बंद कर देता हूं जो तब दायरे से बाहर हो जाते हैं, जिससे इसका विनाशकारी कहलाता है।

क्या यह पर्याप्त बड़ी सूचियों के लिए मेरे ढेर को उड़ा देगा? क्या यह मान लेना उचित है कि कंपाइलर एक जटिल जटिल अनुकूलन (इनलाइन unique_ptrडिस्ट्रक्टर को node's' में करेगा, फिर टेल रीस्क्रिशन का उपयोग करेगा), जो कि अगर मैं निम्नलिखित काम करूं तो बहुत मुश्किल हो जाती है (क्योंकि dataडिस्ट्रॉक्टर इस को बाधित nextकर देगा) कंपाइलर के लिए संभावित रीअॉर्डरिंग और टेल कॉल अवसर पर ध्यान दें):

struct node {
    std::shared_ptr<node> next;
    ComplicatedDestructorClass data;
}

यदि dataकिसी तरह इसके पास एक संकेतक है nodeतो यह पूंछ पुनरावृत्ति के लिए भी असंभव हो सकता है (हालांकि निश्चित रूप से हमें इस तरह के उल्लंघन से बचने का प्रयास करना चाहिए)।

सामान्य तौर पर, कोई इस सूची को नष्ट करने के लिए कैसे माना जाता है, अन्यथा? हम सूची के माध्यम से आगे नहीं बढ़ सकते हैं और "वर्तमान" नोड को हटा सकते हैं क्योंकि साझा सूचक में ए नहीं है release! एकमात्र तरीका एक कस्टम डिलेटर के साथ है, जो वास्तव में मेरे लिए बदबूदार है।


1
दूसरे मामले में बताए गए एनकैप्सुलेशन के उल्लंघन के बिना भी यह लायक है, gcc -O3एक पूंछ पुनरावृत्ति (एक जटिल उदाहरण में) का अनुकूलन करने में असमर्थ था।
VF1

1
वहां आपके पास अपना जवाब है: यह आपके स्टैक को उड़ा सकता है, यदि कंपाइलर पुनरावृत्ति को दूर नहीं कर सकता है।
बार्ट वैन इन्जेन Schenau

@BartvanIngenSchenau मुझे लगता है कि एक और उदाहरण है इस समस्या का । यह वास्तव में शर्म की बात है, क्योंकि मुझे स्मार्ट पॉइंटर स्वच्छता पसंद है।
VF1

जवाबों:


7

हाँ, यह अंत में, अपने ढेर उड़ाने जब तक संकलक बस के लिए एक पूंछ कॉल अनुकूलन लागू करने के लिए होता होगा nodeके नाशक और shared_ptr के नाशक। उत्तरार्द्ध मानक पुस्तकालय कार्यान्वयन पर बेहद निर्भर है। उदाहरण के लिए, माइक्रोसॉफ्ट का एसटीएल कभी ऐसा नहीं करेगा, क्योंकि shared_ptrपहले अपने पॉइंटर की रेफरेंस काउंट को घटाता है (संभवतः ऑब्जेक्ट को नष्ट करता है) और फिर उसके कंट्रोल ब्लॉक के रेफरेंस काउंट (कमजोर रेफरेंस काउंट) को घटाता है। तो आंतरिक विध्वंसक एक पूंछ कॉल नहीं है। यह एक आभासी कॉल भी है , जो इसे कम संभावना बनाता है कि यह अनुकूलित हो जाएगा।

विशिष्ट सूचियों एक नोड होने ही अगले नहीं द्वारा लेकिन एक कंटेनर है कि सभी नोड्स मालिक है, और नाशक में नष्ट सब कुछ करने के लिए एक पाश का उपयोग करता है के द्वारा कि समस्या को हल मिलता है।


हाँ, मैंने shared_ptrअंत में उन लोगों के लिए एक कस्टम डिलेटर के साथ "विशिष्ट" सूची हटाने एल्गोरिथ्म को लागू किया । मैं पूरी तरह से संकेत से छुटकारा नहीं मिल सकता है के बाद से मैं धागा सुरक्षा की जरूरत है।
VF1

मुझे पता नहीं था कि शेयर किए गए पॉइंटर "काउंटर" ऑब्जेक्ट में या तो एक वर्चुअल डिस्ट्रक्टर होगा या नहीं, मैंने हमेशा यह माना कि यह सिर्फ एक पीओडी था जो मजबूत रिफल्स + कमजोर रिफल्स + डीलेटर ...
VF1

@ VF1 क्या आप सुनिश्चित हैं कि संकेत आपको थ्रेड सुरक्षा प्रदान करना चाहते हैं?
सेबस्टियन Redl

हाँ - कि के पूरे मुद्दे है std::atomic_*उन्हें, कोई के लिए भार के?
VF1

हां, लेकिन ऐसा कुछ भी नहीं है जिसे आप std::atomic<node*>बहुत अधिक और सस्ते में हासिल नहीं कर सकते ।
सेबस्टियन रेडल

5

देर से जवाब लेकिन जब से किसी ने इसे प्रदान नहीं किया ... मैं एक ही मुद्दे में भाग गया और एक कस्टम विध्वंसक का उपयोग करके इसे हल किया:

virtual ~node () throw () {
    while (next) {
        next = std::move(next->next);
    }
}

यदि आपके पास वास्तव में एक सूची है , अर्थात प्रत्येक नोड में एक नोड से पहले है और अधिकांश एक अनुयायी है, और आपका listपहला के लिए एक संकेतक है node, तो ऊपर काम करना चाहिए।

यदि आपके पास कुछ फजी संरचना है (जैसे कि चक्रीय ग्राफ), तो आप निम्नलिखित का उपयोग कर सकते हैं:

virtual ~node () throw () {
    while (next && next.use_count() < 2) {
        next = std::move(next->next);
    }
}

विचार यह है कि जब आप करते हैं:

next = std::move(next->next);

पुराना साझा सूचक nextनष्ट हो गया है (क्योंकि use_countअभी है 0), और आप निम्नलिखित को इंगित करते हैं। यह बिल्कुल वही करता है जो डिफ़ॉल्ट विध्वंसक होता है, सिवाय इसके कि यह पुनरावृत्ति के बजाय पुनरावृति करता है और इस प्रकार स्टैक ओवरफ्लो से बचता है।


दिलचस्प विचार है। यह सुनिश्चित नहीं है कि यह थ्रेड-सुरक्षा के लिए ओपी की आवश्यकता को पूरा करता है, लेकिन निश्चित रूप से अन्य संबंध में इस मुद्दे से संपर्क करने का एक अच्छा तरीका है।
जूल्स

एक वास्तविक सूची में, प्रत्येक शर्त जबकि के साथ एक बार अधिक से अधिक मूल्यांकन किया जाएगा, - जब तक आप इस कदम ऑपरेटर अतिभारित, मुझे यकीन है कि कैसे नहीं इस दृष्टिकोण वास्तव में कुछ भी बचाता हूँ next = std::move(next->next)बुला next->~node()रिकर्सिवली।
VF1

1
@ VF1 क्योंकि यह कार्य next->nextअमान्य है (चाल असाइनमेंट ऑपरेटर द्वारा) द्वारा निर्दिष्ट मूल्य से पहले nextनष्ट हो जाता है, इस प्रकार पुनरावृत्ति को "रोक" देता है। मैं वास्तव में इस कोड और इस काम का उपयोग करता हूं (इसके साथ परीक्षण किया गया g++, clangऔर msvc), लेकिन अब जब आप इसे कहते हैं, तो मुझे यकीन नहीं है कि यह मानक द्वारा परिभाषित किया गया है (तथ्य यह है कि स्थानांतरित किए गए सूचक को पुरानी वस्तु के विनाश से पहले अमान्य कर दिया गया है) लक्ष्य सूचक द्वारा)।
होल्ट

@ VF1 अपडेट: मानक के अनुसार, operator=(std::shared_ptr&& r)के बराबर है std::shared_ptr(std::move(r)).swap(*this)। मानक से फिर भी, की चाल निर्माता std::shared_ptr(std::shared_ptr&& r)बनाता rरिक्त होता है, इस प्रकार r(खाली है r.get() == nullptr) करने के लिए कॉल करने से पहले swap। मेरे मामले में, next->nextपुराने साधन द्वारा nextनष्ट किए गए ( swapकॉल द्वारा ) इंगित किए जाने से पहले यह साधन खाली है ।
होल्ट

1
@ VF1 आपका कोड समान नहीं है - कॉल fचालू है next, नहीं next->next, और चूंकि next->nextशून्य है, यह तुरंत बंद हो जाता है।
होल्ट

1

ईमानदार होने के लिए मैं किसी भी सी ++ कंपाइलर के स्मार्ट पॉइंटर डीलक्लोलेशन एल्गोरिथ्म से परिचित नहीं हूं, लेकिन मैं एक सरल, गैर-पुनरावर्ती एल्गोरिदम की कल्पना कर सकता हूं जो ऐसा करता है। इस पर विचार करो:

  • आपके पास निपटारे के लिए इंतजार कर रहे स्मार्ट पॉइंटर्स की एक कतार है।
  • आपके पास एक फ़ंक्शन है जो पहले पॉइंटर लेता है और इसे डीलॉलेट करता है, और इसे तब तक दोहराता है जब तक कि कतार खाली न हो जाए।
  • यदि किसी स्मार्ट पॉइंटर को डीलक्लोशन की आवश्यकता होती है, तो इसे कतार में धकेल दिया जाता है और उपरोक्त फ़ंक्शन को कॉल किया जाता है।

इसलिए स्टैक को ओवरफ्लो करने का कोई मौका नहीं होगा, और यह बहुत सरल है जो एक पुनरावर्ती एल्गोरिदम का अनुकूलन कर रहा है।

मुझे यकीन नहीं है कि यह "लगभग शून्य लागत स्मार्ट पॉइंटर्स" दर्शन में फिट बैठता है।

मुझे लगता है कि होगा आपको लगता है कि क्या वर्णित ढेर अतिप्रवाह कारण नहीं है, लेकिन आप मुझे गलत साबित करने के लिए एक चतुर प्रयोग के निर्माण के लिए कोशिश कर सकते हैं।

अपडेट करें

खैर, यह गलत साबित होता है जो मैंने पहले लिखा था:

#include <iostream>
#include <memory>

using namespace std;

class Node;

Node *last;
long i;

class Node
{
public:
   unique_ptr<Node> next;
   ~Node()
   {
     last->next.reset(new Node);
     last = last->next.get();
     cout << i++ << endl;
   }
};

void ignite()
{
    Node n;
    n.next.reset(new Node);
    last = n.next.get();
}

int main()
{
    i = 0;
    ignite();
    return 0;
}

यह कार्यक्रम अनंत रूप से बनाता है, और नोड्स की एक श्रृंखला को डिकंस्ट्रक्ट करता है। यह स्टैक ओवरफ्लो का कारण बनता है।


1
आह, आप निरंतरता-गुजर शैली का उपयोग करने का मतलब है? प्रभावी ढंग से, कि तुम क्या वर्णन कर रहे हैं। हालांकि, मैं जितनी जल्दी से स्मार्ट संकेत दिए गए बलिदान था ढेर पर एक और सूची को बनाने के सिर्फ एक पुराने एक पुनःआवंटन करने के लिए।
VF1

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