मुझे इंजन घटकों के लिए कंस्ट्रक्टर और डिस्ट्रक्टर में तर्क रखने के बजाय अलग-अलग इनिशियलाइज़ेशन और क्लीन अप विधियों का उपयोग क्यों करना चाहिए?


9

मैं अपने गेम इंजन पर काम कर रहा हूं, और मैं वर्तमान में अपने प्रबंधकों को डिजाइन कर रहा हूं। मैंने पढ़ा है कि स्मृति प्रबंधन के लिए, उपयोग करना Init()और CleanUp()फ़ंक्शंस बेहतर हैं फिर कंस्ट्रक्टर और डिस्ट्रक्टर्स का उपयोग करना।

मैं सी ++ कोड उदाहरणों की तलाश कर रहा हूं, यह देखने के लिए कि वे फ़ंक्शन कैसे काम करते हैं और मैं उन्हें अपने इंजन में कैसे लागू कर सकता हूं। कैसे Init()और CleanUp()काम करता है , और मैं उन्हें अपने इंजन में कैसे लागू कर सकता हूं?



C ++ के लिए, stackoverflow.com/questions/3786853/… देखें। Init () का उपयोग करने के मुख्य कारण हैं 1) निर्माण कार्यों में अपवादों को रोकें और सहायक कार्यों के साथ क्रैश 2) व्युत्पन्न वर्ग 3 से आभासी तरीकों का उपयोग करने में सक्षम होना: परिपत्र परिपत्र निर्भरताएं 4) कोड दोहराव से बचने के लिए निजी विधि के रूप में
brita_

जवाबों:


12

यह वास्तव में बहुत आसान है:

कंस्ट्रक्टर होने के बजाय जो आपका सेटअप करता है,

// c-family pseudo-code
public class Thing {
    public Thing (a, b, c, d) { this.x = a; this.y = b; /* ... */ }
}

... क्या आपका कंस्ट्रक्टर बहुत कम है या कुछ भी नहीं है, और एक विधि लिखी जाती है .initया कहा जाता है .initialize, जो कि आपका कंस्ट्रक्टर सामान्य रूप से करता है।

public class Thing {
    public Thing () {}
    public void initialize (a, b, c, d) {
        this.x = a; /*...*/
    }
}

तो अब बस के बजाय जा रहा है जैसे:

Thing thing = new Thing(1, 2, 3, 4);

तुम जा सकते हो:

Thing thing = new Thing();

thing.doSomething();
thing.bind_events(evt_1, evt_2);
thing.initialize(1, 2, 3, 4);

वहाँ लाभ यह है कि अब आप अपने सिस्टम में अधिक आसानी से निर्भरता-इंजेक्शन / उलटा नियंत्रण का उपयोग कर सकते हैं।

कहने के बजाय

public class Soldier {
    private Weapon weapon;

    public Soldier (name, x, y) {
        this.weapon = new Weapon();
    }
}

आप सैनिक का निर्माण कर सकते हैं, उसे एक सुसज्जित विधि दे सकते हैं, जहाँ आप उसे एक हथियार सौंप सकते हैं , और बाकी सभी निर्माण कार्यों को कॉल कर सकते हैं।

इसलिए अब, दुश्मनों को अधीन करने के बजाय, जहां एक सैनिक के पास एक पिस्तौल है और दूसरे के पास एक राइफल है और दूसरे के पास एक बन्दूक है, और यही एकमात्र अंतर है, आप बस कह सकते हैं:

Soldier soldier1 = new Soldier(),
        soldier2 = new Soldier(),
        soldier3 = new Soldier();

soldier1.equip(new Pistol());
soldier2.equip(new Rifle());
soldier3.equip(new Shotgun());

soldier1.initialize("Bob",  32,  48);
soldier2.initialize("Doug", 57, 200);
soldier3.initialize("Mike", 92,  30);

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

संपादित करें


जैसा कि क्रियोटन ने नीचे बताया है, यह मूल पोस्ट के "हाउ" का उत्तर देता है , लेकिन वास्तव में एक "क्यों" का अच्छा काम नहीं करता है।

जैसा कि आप शायद ऊपर दिए गए उत्तर में देख सकते हैं, इसमें बहुत अंतर नहीं हो सकता है:

var myObj = new Object();
myObj.setPrecondition(1);
myObj.setOtherPrecondition(2);
myObj.init();

और लेखन

var myObj = new Object(1,2);

जबकि बस एक बड़ा निर्माता कार्य कर रहा है।
उन वस्तुओं के लिए एक तर्क दिया जाना चाहिए जिनकी 15 या 20 पूर्व-स्थितियां हैं, जो एक निर्माणकर्ता को काम करने के लिए बहुत कठिन बनाता है, और यह उन चीजों को देखने और याद रखने में आसान बनाता है, जो उन चीजों को इंटरफ़ेस में खींच कर निकालते हैं। , ताकि आप देख सकें कि तात्कालिकता कैसे काम करती है, एक स्तर अधिक।

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

उस ने कहा, संभावना है, अगर आप अपने निर्माणकर्ता में एक तर्क सूची के साथ काम कर रहे हैं, कि आपकी वस्तु बहुत बड़ी है और बहुत अधिक है, जैसा कि है। फिर से, यह एक व्यक्तिगत-प्राथमिकता वाली चीज है, और दूर-दूर तक अपवाद हैं, लेकिन यदि आप 20 चीजों को एक वस्तु में पास कर रहे हैं, तो संभावना अच्छी है कि आप उस वस्तु को कम करने का एक तरीका ढूंढ सकते हैं, छोटी वस्तुओं को बनाकर ।

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

आप जानते हैं कि आपको वस्तु की आवश्यकता है, इसलिए आप इसे वैसे भी बनाने जा रहे हैं, लेकिन इसे ठीक से काम करने के लिए, इसे सर्वर से या किसी अन्य फ़ाइल से डेटा की आवश्यकता होती है, जिसे अब लोड करना होगा।

फिर, चाहे आप आवश्यक डेटा को एक विशाल init में पास कर रहे हों, या एक इंटरफ़ेस का निर्माण कर रहे हों, अवधारणा के लिए वास्तव में महत्वपूर्ण नहीं है, जितना कि यह आपके ऑब्जेक्ट के इंटरफ़ेस और आपके सिस्टम के डिज़ाइन के लिए महत्वपूर्ण है ...

लेकिन वस्तु के निर्माण के संदर्भ में, आप ऐसा कुछ कर सकते हैं:

var obj_w_async_dependencies = new Object();
async_loader.load(obj_w_async_dependencies.async_data, obj_w_async_dependencies);

async_loader एक फ़ाइलनाम, या एक संसाधन नाम या जो कुछ भी हो सकता है, उस संसाधन को लोड करें - शायद यह ध्वनि फ़ाइलों, या छवि डेटा को लोड करता है, या शायद यह सहेजे गए चरित्र आँकड़े लोड करता है ...

... और फिर यह उस डेटा को वापस फीड करेगा obj_w_async_dependencies.init(result);

इस तरह का डायनामिक वेब-एप्स में अक्सर पाया जाता है।
जरूरी नहीं कि किसी वस्तु के निर्माण में, उच्च-स्तरीय अनुप्रयोगों के लिए: उदाहरण के लिए, दीर्घाएँ तुरंत लोड हो सकती हैं और प्रारंभ हो सकती हैं, और फिर वे फ़ोटो को प्रदर्शित करते हैं जैसे कि वे स्ट्रीम करते हैं - यह वास्तव में एक async-initialization नहीं है, लेकिन जहां इसे अधिक बार देखा जाता है जावास्क्रिप्ट पुस्तकालयों में।

एक मॉड्यूल दूसरे पर निर्भर हो सकता है, और इसलिए जब तक आश्रितों का लोड पूरा नहीं हो जाता है, तब तक उस मॉड्यूल के आरंभीकरण को स्थगित किया जा सकता है।

इस के खेल-विशिष्ट उदाहरणों के संदर्भ में, एक वास्तविक Gameवर्ग पर विचार करें ।

हम निर्माता में .startया कॉल क्यों नहीं कर सकते .run?
संसाधनों को लोड करने की आवश्यकता है - बाकी सब कुछ बहुत अधिक परिभाषित किया गया है और जाने के लिए अच्छा है, लेकिन अगर हम बिना डेटाबेस-कनेक्शन के, या बनावट या मॉडल या आवाज़ या स्तरों के बिना गेम चलाने की कोशिश करते हैं, तो यह नहीं होगा एक विशेष रूप से दिलचस्प खेल ...

... तो क्या, फिर हम जो देखते हैं, उसके बीच का अंतर है Game, सिवाय इसके कि हम इसकी "आगे बढ़ें" विधि को एक नाम दें, जो कि अधिक दिलचस्प है .init(या इसके विपरीत, आरंभ को और भी अलग कर सकता है, अलग लोडिंग के लिए, जो चीजें लोड की गई हैं, उन्हें सेट करना और जब सब कुछ सेट हो चुका हो तो प्रोग्राम चलाना)।


2
" आप तब उन्हें मैन्युअल रूप से कॉल करेंगे, ताकि आप यह जान सकें कि प्रोग्राम में कब और कहां क्या हो रहा है। " C ++ में एकमात्र समय जहां एक विध्वंसक को अंतर्निहित रूप से स्टैक ऑब्जेक्ट (या ग्लोबल) के लिए कहा जाता है। ढेर आवंटित वस्तुओं को स्पष्ट विनाश की आवश्यकता होती है। इसलिए यह हमेशा स्पष्ट होता है जब वस्तु को निपटाया जा रहा है।
निकोल बोलस

6
यह कहना बिल्कुल सटीक नहीं है कि विभिन्न हथियार प्रकारों के इंजेक्शन को सक्षम करने के लिए आपको इस अलग तरीके की आवश्यकता है, या यह कि उपवर्गों के प्रसार से बचने का एकमात्र तरीका है। आप हथियार उदाहरणों को कंस्ट्रक्टर के माध्यम से पास कर सकते हैं! इसलिए यह मेरे लिए -1 है क्योंकि यह सम्मोहक उपयोग का मामला नहीं है।
काइलोटन

1
-1 मुझ से भी, काइलोटन के समान कारणों से। आप एक बहुत सम्मोहक तर्क नहीं देते हैं, यह सब निर्माणकर्ताओं के साथ किया जा सकता है।
पॉल मंटा

हां, यह निर्माणकर्ताओं और विध्वंसक के साथ पूरा किया जा सकता है। उन्होंने एक तकनीक के उपयोग के मामलों के लिए कहा कि वे क्यों और कैसे काम करते हैं या क्यों करते हैं। घटक-आधारित प्रणाली होने से जहां आपके पास डीआईआर के लिए सेटर / बाइंडिंग विधियां, बनाम कंस्ट्रक्टर-पास पैरामीटर हैं, वास्तव में सभी नीचे आते हैं कि आप अपना इंटरफ़ेस कैसे बनाना चाहते हैं। लेकिन अगर आपकी वस्तु को 20 आईओसी घटकों की आवश्यकता है, तो क्या आप उन सभी को अपने कंस्ट्रक्टर में डालना चाहते हैं? क्या आप? निःसंदेह तुमसे हो सकता है। अगर आप? शायद शायद नहीं। यदि आप नहीं चुनते हैं , तो क्या आपको इसकी आवश्यकता है .init, शायद नहीं, लेकिन संभावना है। एर्गो, वैध मामला।
नॉरगार्ड

1
@Kylotan मैं वास्तव में सवाल का शीर्षक संपादित करने के लिए क्यों पूछ रहा हूँ। ओपी ने केवल "कैसे" पूछा। मैंने "क्यों" को "कैसे" के रूप में शामिल करने के लिए प्रश्न को आगे बढ़ाया, जो किसी को भी प्रोग्रामिंग के बारे में कुछ भी जानता है ("बस तर्क को आप ctor में एक अलग फ़ंक्शन में ले जाएंगे और इसे कॉल करें") और "क्यों" अधिक रोचक / सामान्य है।
टेट्राद

17

आपने जो भी पढ़ा है, उसमें कहा गया है कि Init और CleanUp बेहतर है, आपको यह भी बताना चाहिए कि क्यों। ऐसे लेख जो उनके दावों को सही नहीं ठहराते, वे पढ़ने लायक नहीं हैं।

अलग-अलग प्रारंभिककरण और शटडाउन फ़ंक्शन होने से सिस्टम को सेट करना और नष्ट करना आसान हो सकता है क्योंकि आप चुन सकते हैं कि उन्हें किस क्रम में कॉल करना है, जबकि ऑब्जेक्ट के निर्माण के दौरान कंस्ट्रक्टर को बिल्कुल बुलाया जाता है और ऑब्जेक्ट के नष्ट होने पर विध्वंसक कहते हैं। जब आपके पास 2 वस्तुओं के बीच जटिल निर्भरता होती है, तो आपको अक्सर खुद को स्थापित करने से पहले दोनों की आवश्यकता होती है - लेकिन अक्सर यह कहीं और खराब डिजाइन का संकेत है।

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


धन्यवाद, लेकिन मैं मुख्य रूप से उदाहरणों की तलाश कर रहा हूं, क्योंकि लेख उनके पास नहीं था। अगर मेरा सवाल उस बारे में स्पष्ट नहीं था, तो मैं माफी चाहता हूं, लेकिन मैंने इसे अभी संपादित किया है।
फ्रिसो

3

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

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

Rq: यदि आपके पास सब कुछ रीसेट हो जाता है, तो आपको अपने पूल किए गए ऑब्जेक्ट के लिए क्लीनअप विधि की आवश्यकता नहीं हो सकती है।

संपादित करें: इस पोस्ट के जवाब ने मुझे जावास्क्रिप्ट में पूलिंग के बारे में किए गए एक छोटे से लेख को अंतिम रूप देने के लिए प्रेरित किया ।
यदि आप रुचि रखते हैं तो आप इसे यहां पा सकते हैं:
http://gamealchemist.wordpress.com/


1
-1: आपको केवल वस्तुओं का एक पूल होने के लिए ऐसा करने की आवश्यकता नहीं है। आप ऐसा कर सकते हैं कि एक नए विनाशकारी कॉल द्वारा प्लेसमेंट नए के माध्यम से निर्माण से आवंटन को अलग कर दिया जाए और उसे हटा दिया जाए। तो यह कुछ इनिशिएटिव विधि से कंस्ट्रक्टर / डिस्ट्रक्टर्स को अलग करने का एक वैध कारण नहीं है।
निकोल बोलस

प्लेसमेंट नया C ++ विशिष्ट है, और थोड़ा गूढ़ भी है।
काइलोटन

+1 यह c + में दूसरे तरीके से करना संभव हो सकता है। लेकिन अन्य भाषाओं में नहीं ... और शायद यही कारण है कि मैं गेमबजेक्ट्स पर इनिट विधि का उपयोग करूंगा।
काइकिमारु

1
@ निकोल बोलस: मुझे लगता है कि आप ओवररिएक्ट कर रहे हैं। तथ्य यह है कि पूलिंग करने के अन्य तरीके हैं (आप एक जटिल का उल्लेख करते हैं, सी ++ के लिए विशिष्ट है) इस तथ्य को अमान्य नहीं करता है कि एक अलग Init का उपयोग करना कई भाषाओं में पूलिंग को लागू करने का एक अच्छा और सरल तरीका है। मेरी प्राथमिकताएँ GameDev पर, अधिक सामान्य उत्तरों के लिए जाती हैं।
GameAlchemist

@VincentPiel: C ++ में प्लेसमेंट नए और ऐसे "जटिल" का उपयोग कैसे किया जा रहा है? इसके अलावा, यदि आप GC भाषा में काम कर रहे हैं, तो संभावनाएं अच्छी हैं कि वस्तुओं में GC- आधारित वस्तुएँ होंगी। तो क्या उनमें से प्रत्येक को भी मतदान करना होगा? इस प्रकार, एक नई वस्तु बनाने में पूल से नई वस्तुओं का एक गुच्छा प्राप्त करना शामिल होगा।
निकोल बोल

0

आपका सवाल उल्टा है ... ऐतिहासिक रूप से, अधिक प्रासंगिक सवाल यह है:

क्यों है निर्माण + intialisation सम्मिश्रण , यानी क्यों नहीं है हम इन चरणों का अलग से करते हैं? निश्चित रूप से यह SoC के खिलाफ जाता है ?

C ++ के लिए, RAII का अभिप्राय यह है कि संसाधन अधिग्रहण और रिलीज़ को सीधे आजीवन के लिए बाँध दिया जाए, इस उम्मीद में कि यह संसाधन रिलीज़ को सुनिश्चित करेगा। क्या यह? आंशिक रूप से। यह स्टैक-आधारित / स्वचालित चर के संदर्भ में 100% पूरा होता है, जहां संबंधित गुंजाइश को स्वचालित रूप से विध्वंसक कहता है / इन चर (इसलिए क्वालीफायर automatic) को जारी करता है। हालांकि ढेर चर के लिए, यह बहुत उपयोगी पैटर्न दुखद रूप से टूट जाता है, क्योंकि आप अभी भी deleteविध्वंसक को चलाने के लिए स्पष्ट रूप से कॉल करने के लिए मजबूर हैं , और यदि आप ऐसा करना भूल जाते हैं, तो आप अभी भी काट लेंगे जो RAII हल करने का प्रयास करता है; ढेर-आवंटित चर के संदर्भ में, फिर, C ++ सी ( deleteबनाम) पर सीमित लाभ प्रदान करता हैfree()) आरंभिक निर्माण के साथ निर्माण करते समय, जो निम्नलिखित के संदर्भ में नकारात्मक प्रभाव डालता है:

C में गेम / सिमुलेशन के लिए एक ऑब्जेक्ट सिस्टम का निर्माण करने की दृढ़ता से अनुशंसा की जाती है कि यह RAII और इस तरह के अन्य OO-केंद्रित पैटर्न की सीमाओं पर प्रकाश की एक बड़ी मात्रा को बहा देगा, धारणाओं की गहन समझ के माध्यम से जो C ++ और बाद में शास्त्रीय OO बनाते हैं (याद रखें कि C ++ एक OO सिस्टम के रूप में C में निर्मित हुआ)।

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