कैसे खेल वस्तुओं से बचने के लिए गलती से खुद को सी ++ में हटा रहे हैं


20

मान लीजिए कि मेरे खेल में एक राक्षस है जो खिलाड़ी पर विस्फोट कर सकता है। चलो यादृच्छिक पर इस राक्षस के लिए एक नाम चुनें: एक लता। तो, Creeperकक्षा में एक विधि है जो कुछ इस तरह दिखती है:

void Creeper::kamikaze() {
    EventSystem::postEvent(ENTITY_DEATH, this);

    Explosion* e = new Explosion;
    e->setLocation(this->location());
    this->world->addEntity(e);
}

घटनाओं को कतारबद्ध नहीं किया जाता है, उन्हें तुरंत भेज दिया जाता है। यह Creeperऑब्जेक्ट को कॉल के अंदर कहीं डिलीट होने का कारण बनता है postEvent। कुछ इस तरह:

void World::handleEvent(int type, void* context) {
    if(type == ENTITY_DEATH){
        Entity* ent = dynamic_cast<Entity*>(context);
        removeEntity(ent);
        delete ent;
    }
}

चूँकि Creeperऑब्जेक्ट तब भी डिलीट हो जाता है जबकि kamikazeविधि अभी भी चल रही है, यह एक्सेस करने की कोशिश करने पर क्रैश हो जाएगा this->location()

एक समाधान घटनाओं को एक बफर में कतारबद्ध करना और बाद में उन्हें भेजना है। क्या C ++ गेम्स में यह सामान्य समाधान है? यह एक हैक की तरह महसूस होता है, लेकिन यह सिर्फ इसलिए हो सकता है क्योंकि विभिन्न स्मृति प्रबंधन प्रथाओं के साथ अन्य भाषाओं के साथ मेरा अनुभव।

C ++ में, क्या इस समस्या का एक बेहतर सामान्य समाधान है जहां कोई वस्तु गलती से अपने ही तरीकों में से एक को हटा देती है?


6
उह, आप शुरुआत के बजाय कैसे कामिकेज़ पद्धति के अंत में पोस्टवेंट को कॉल करते हैं?
हैकवर्थ

@Hackworth जो इस विशिष्ट उदाहरण के लिए काम करेगा, लेकिन मैं एक अधिक सामान्य समाधान की तलाश कर रहा हूं। मैं कहीं से भी घटनाओं को पोस्ट करने में सक्षम होना चाहता हूं और क्रैश होने का डर नहीं होना चाहिए।
टॉम डेलिंग

आप autoreleaseऑब्जेक्टिव-सी के कार्यान्वयन पर भी एक नज़र डाल सकते हैं , जहां "बस थोड़ा सा" तक विलोपन किया जाता है।
क्रिस बर्ट-ब्राउन

जवाबों:


40

हटाओ मत this

यहां तक ​​कि निहितार्थ भी।

- कभी -

किसी ऑब्जेक्ट को हटाना, जबकि उसका एक सदस्य कार्य अभी भी ढेर पर है, परेशानी के लिए भीख माँग रहा है। कोई भी कोड आर्किटेक्चर जिसके परिणामस्वरूप होता है ("गलती से" या नहीं) उद्देश्यपूर्ण रूप से खराब है , खतरनाक है , और इसे तुरंत रिफैक्ट किया जाना चाहिए । इस स्थिति में, यदि आपके राक्षस को 'वर्ल्ड :: हैंडल' कॉल करने की अनुमति दी जा रही है, तो किसी भी परिस्थिति में, उस फ़ंक्शन के अंदर राक्षस को हटा दें!

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


6
कोष्ठक में आपके उत्तर का दूसरा भाग सहायक था। झंडे के लिए मतदान एक अच्छा उपाय है। लेकिन, मैं पूछ रहा था कि इसे करने से कैसे बचें, न कि यह एक अच्छा या बुरा काम था। यदि कोई प्रश्न पूछता है कि " एक्स एक्सिडेंटली करने से मैं कैसे बच सकता हूँ? ", और आपका जवाब है " नेवर एक्स एक्स, एक्सीडेंटली ", बोल्ड में, जो वास्तव में प्रश्न का उत्तर नहीं देता है।
टॉम डेलिंग

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

3
@TrevorPowell मैं यह नहीं कह रहा हूं कि आप गलत हैं। वास्तव में, मैं आपसे सहमत हूं। मैं सिर्फ यह कह रहा हूं कि यह वास्तव में पूछे गए सवाल का जवाब नहीं देता है। यह ऐसा है जैसे आपने मुझसे पूछा कि "मुझे अपने गेम में ऑडियो कैसे मिलेगा? " और मेरा जवाब था " मुझे विश्वास नहीं हो रहा है कि आपके पास ऑडियो नहीं है। अभी अपने गेम में ऑडियो डालें। " डाल " (आप FMOD का उपयोग कर सकते हैं) ," जो एक वास्तविक उत्तर है।
टॉम डेलिंग

6
@TrevorPowell यह वह जगह है जहाँ आप गलत हैं। यह "सिर्फ अनुशासन" नहीं है अगर मुझे कोई विकल्प नहीं पता है। उदाहरण कोड मैंने दिया है विशुद्ध रूप से सैद्धांतिक है। मैं पहले से ही जानता हूं कि यह एक खराब डिजाइन है, लेकिन मेरा सी ++ रस्टी है, इसलिए मैंने सोचा कि मैं बेहतर डिजाइन के बारे में पूछूंगा इससे पहले कि मैं वास्तव में कोड चाहता हूं कि मैं क्या चाहता हूं। इसलिए मैं वैकल्पिक डिजाइनों के बारे में पूछने आया। " एक विलोपन ध्वज जोड़ें " एक विकल्प है। " कभी मत करो " कोई विकल्प नहीं है । यह मुझे वही बता रहा है जो मैं पहले से जानता हूं। ऐसा लगता है जैसे आपने प्रश्न को ठीक से पढ़े बिना ही उत्तर लिख दिया।
टॉम डेलिंग

4
@ बॉबी का सवाल है "एक्स कैसे न करें"। सीधे शब्दों में कहें तो "डू नॉट एक्स" एक बेकार जवाब है। यदि प्रश्न "मैं एक्स कर रहा हूं" या "मैं एक्स करने के बारे में सोच रहा था" या उस के किसी भी प्रकार का सवाल है, तो यह मेटा चर्चा के मापदंडों को पूरा करेगा, लेकिन वर्तमान रूप में नहीं।
यहोशू ड्रेक

21

एक बफर में घटनाओं की कतार के बजाय, एक बफर में विलोपन कतार । विलंबित-विलोपन में तर्क को सरल रूप से सरल बनाने की क्षमता है; आप वास्तव में एक फ्रेम के अंत या शुरुआत में मेमोरी को मुक्त कर सकते हैं जब आप जानते हैं कि कुछ भी दिलचस्प नहीं है जो आपकी वस्तुओं के लिए हो रहा है, और बिल्कुल कहीं से भी हटाएं।


1
अजीब बात है कि आप इसका उल्लेख करते हैं, क्योंकि मैं सोच रहा था कि NSAutoreleasePoolइस स्थिति में ऑब्जेक्टिव-सी से कितना अच्छा होगा। DeletionPoolC ++ टेम्प्लेट या कुछ और के साथ बनाना पड़ सकता है ।
टॉम डेलिंग

@TomDalling यदि आप बफर को ऑब्जेक्ट के लिए बाहरी बनाते हैं तो सावधान रहने वाली एक बात यह है कि एक ऑब्जेक्ट को एक फ्रेम पर कई कारणों से हटाया जा सकता है, और इसे कई बार हटाने की कोशिश करना संभव होगा।
जॉन Calsbeek

सच सच। मुझे पॉइंटर्स को एक std :: सेट में रखना होगा।
टॉम डेलिंग

5
हटाने के लिए ऑब्जेक्ट्स के एक बफ़र के बजाय, आप ऑब्जेक्ट में एक ध्वज भी सेट कर सकते हैं। एक बार जब आपको एहसास होने लगता है कि आप रनिंग के दौरान नए कॉल या डिलीट से बचना चाहते हैं और ऑब्जेक्ट पूल में चले जाते हैं, तो यह सरल और तेज़ दोनों होगा।
सीन मिडिलिचच

4

दुनिया को हटाने की अनुमति देने के बजाय आप सभी हटाए गए संस्थाओं को संग्रहीत करने के लिए बाल्टी के रूप में सेवा करने के लिए एक और वर्ग का उदाहरण दे सकते हैं। इस विशेष घटना को ENTITY_DEATHघटनाओं को सुनना चाहिए और उन्हें ऐसे संभालना चाहिए कि यह उन्हें कतार में खड़ा कर दे। Worldके बाद फ्रेम गाया गया है और 'स्पष्ट' इस बाल्टी, जो बारी में संस्थाओं उदाहरणों की वास्तविक विलोपन प्रदर्शन करेंगे तो पुनरावृति इन उदाहरणों से अधिक और बाद मौत कार्रवाई कर सकते हैं।

इस तरह के एक वर्ग का एक उदाहरण इस तरह होगा: http://ideone.com/7Upza


+1, यह सीधे संस्थाओं को फ़्लैग करने का एक विकल्प है। अधिक सीधे तौर पर, बस एक जीवित-सूची और सीधे Worldवर्ग में संस्थाओं की एक मृत-सूची है ।
लॉरेंट कौविदो

2

मैं एक ऐसी फैक्ट्री को लागू करने का सुझाव दूंगा जिसका उपयोग खेल के सभी खेल ऑब्जेक्ट आवंटन के लिए किया जाता है। इसलिए खुद को नया कहने के बजाय, आप कारखाने को आपके लिए कुछ बनाने के लिए कहेंगे।

उदाहरण के लिए

Object* o = gFactory->Create("Explosion");

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

एक फ्रेम की देरी के साथ सभी संदेश भेजने पर भी विचार करें। केवल अपवादों की एक जोड़ी है, जहां आपको तुरंत भेजने की जरूरत है, लेकिन अधिकांश मामलों में बस


2

आप C-++ में प्रबंधित-मेमोरी को स्वयं कार्यान्वित कर सकते हैं, ताकि जब ENTITY_DEATHकॉल किया जाता है , तो वह सब होता है, इसके संदर्भों की संख्या एक से कम हो जाती है।

बाद में @ जोंह ने सुझाव दिया कि हर फ्रेम की भीख माँगने पर आप जाँच सकते हैं कि कौन सी इकाइयाँ बेकार हैं (शून्य संदर्भ वाले) और उन्हें हटा दें। उदाहरण के लिए आप यहाँboost::shared_ptr<T> ( प्रलेखित ) का उपयोग कर सकते हैं या यदि आप C ++ 11 (VC2010) का उपयोग कर रहे हैंstd::tr1::shared_ptr<T>


बस std::shared_ptr<T>, तकनीकी रिपोर्ट नहीं! - आपको एक कस्टम डीलेटर निर्दिष्ट करना होगा, अन्यथा यह संदर्भ गणना शून्य तक पहुंचने पर तुरंत ऑब्जेक्ट को हटा देगा।
लेफ्टरनबाउट

1
@leftaroundabout यह वास्तव में निर्भर करता है, कम से कम मुझे gcc में tr1 का उपयोग करने की आवश्यकता है। लेकिन वीसी में उस की कोई जरूरत नहीं थी।
अली

2

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


1

हमने एक गेम में जो किया वह था प्लेसमेंट नया

SomeEvent* obj = new(eventPool.alloc()) new SomeEvent();

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

जिस तरह से हमारे ईवेंट सिस्टम ने काम किया उसके कारण हमें डिस्ट्रक्टर्स को इवेट्स पर कॉल करने की आवश्यकता नहीं थी। तो पूल केवल मेमोरी के ब्लॉक को मुफ्त में पंजीकृत करेगा, और इसे आवंटित करेगा।

इससे हमें बहुत बड़ी गति मिली।

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

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