วิธีหลีกเลี่ยงวัตถุเกมลบโดยไม่ตั้งใจใน C ++


20

สมมติว่าเกมของฉันมีสัตว์ประหลาดที่สามารถโจมตีได้ด้วยระเบิด เรามาเลือกชื่อสัตว์ประหลาดตัวนี้แบบสุ่ม: Creeper ดังนั้น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วัตถุที่ได้รับการลบในขณะที่วิธีการที่จะยังคงทำงานก็จะมีปัญหาเมื่อพยายามที่จะเข้าถึงkamikazethis->location()

ทางออกหนึ่งคือการจัดคิวเหตุการณ์ลงในบัฟเฟอร์และส่งต่อในภายหลัง นั่นเป็นวิธีแก้ปัญหาทั่วไปในเกม C ++ ใช่ไหม รู้สึกเหมือนแฮ็คสักหน่อย แต่นั่นอาจเป็นเพราะประสบการณ์ของฉันกับภาษาอื่นที่มีการจัดการหน่วยความจำที่แตกต่างกัน

ใน C ++ มีวิธีแก้ปัญหาทั่วไปที่ดีกว่าสำหรับปัญหานี้หรือไม่ที่วัตถุจะลบตัวเองออกจากภายในหนึ่งในวิธีการใด ๆ


6
เอ่อคุณเรียก postEvent ที่ส่วนท้ายของวิธีการ kamikaze แทนที่จะเริ่มได้อย่างไร
Hackworth

@ แฮ็คเวิร์ ธ ที่ใช้งานกับตัวอย่างเฉพาะนี้ได้ แต่ฉันกำลังมองหาวิธีแก้ปัญหาที่กว้างกว่านี้ ฉันต้องการโพสต์กิจกรรมได้ทุกที่และไม่ต้องกลัวว่าจะเกิดปัญหา
Tom Dalling

นอกจากนี้คุณยังสามารถดูการใช้งานautoreleaseใน Objective-C ซึ่งการลบจะถูกระงับไว้สำหรับ "เพียงเล็กน้อย"
Chris Burt-Brown

คำตอบ:


40

อย่าลบ this

แม้โดยปริยาย

- เคย -

การลบวัตถุในขณะที่หนึ่งในฟังก์ชั่นสมาชิกของมันยังคงอยู่ในสแต็กกำลังขอให้มีปัญหา สถาปัตยกรรมโค้ดใด ๆ ที่ส่งผลให้เกิดขึ้น ("บังเอิญ" หรือไม่) นั้นไม่ดีตามวัตถุประสงค์มีอันตรายและควรได้รับการเปลี่ยนใหม่ทันทีทันที ในกรณีนี้ถ้าสัตว์ประหลาดของคุณจะได้รับอนุญาตให้โทร 'World :: handleEvent' อย่าลบสัตว์ประหลาดในฟังก์ชั่นนั้นไม่ว่าในกรณีใด!

(ในสถานการณ์ที่เฉพาะเจาะจงวิธีการตามปกติของฉันคือให้สัตว์ประหลาดตั้งธง 'ตาย' ไว้บนตัวเองและมีวัตถุโลก - หรืออะไรทำนองนั้น - ทดสอบว่าธง 'ตาย' นั้นหนึ่งครั้งต่อเฟรมนำวัตถุเหล่านั้นออก จากรายการวัตถุในโลกและลบมันหรือส่งคืนไปยังสระสัตว์ประหลาดหรือสิ่งอื่น ๆ ที่เหมาะสม ณ เวลานี้โลกยังส่งการแจ้งเตือนเกี่ยวกับการลบดังนั้นวัตถุอื่น ๆ ในโลกจึงรู้ว่าสัตว์ประหลาดนั้นมี หยุดอยู่แล้วและสามารถวางพอยน์เตอร์ใด ๆ ไว้เพื่อที่พวกเขาอาจจะถือโลกนี้ทำในเวลาที่ปลอดภัยเมื่อมันรู้ว่าไม่มีวัตถุใดกำลังประมวลผลอยู่ในขณะนี้ดังนั้นคุณไม่ต้องกังวลเกี่ยวกับสแต็กที่คลี่คลายไปยังจุด ที่ตัวชี้ 'นี้' ชี้ไปที่หน่วยความจำที่เป็นอิสระ)


6
คำตอบครึ่งหลังของคุณในวงเล็บเป็นประโยชน์ การสำรวจความคิดเห็นสำหรับธงเป็นทางออกที่ดี แต่ฉันถามว่าจะหลีกเลี่ยงการทำมันอย่างไรไม่ว่าจะเป็นเรื่องดีหรือไม่ดี หากคำถามถามว่า " ฉันจะหลีกเลี่ยงการทำ X โดยไม่ตั้งใจได้อย่างไร " และคำตอบของคุณคือ " ไม่เคยทำ X เลยโดยไม่ตั้งใจแม้แต่น้อย " เป็นตัวหนานั่นไม่ได้ตอบคำถามจริงๆ
Tom Dalling

7
ฉันยืนอยู่ข้างหลังความคิดเห็นที่ฉันทำในช่วงครึ่งแรกของคำตอบของฉันและฉันรู้สึกว่าพวกเขาตอบคำถามอย่างเต็มที่ตามที่กล่าวไว้ในตอนแรก จุดสำคัญที่ฉันจะทำซ้ำที่นี่คือวัตถุไม่ลบตัวเอง เคย ไม่เรียกคนอื่นให้ลบ เคย แต่คุณต้องมีอย่างอื่นนอกวัตถุซึ่งเป็นเจ้าของวัตถุและมีหน้าที่รับผิดชอบในการสังเกตเมื่อวัตถุต้องถูกทำลาย นี่ไม่ใช่สิ่ง "เพียงแค่สัตว์ประหลาดตาย" นี่เป็นรหัส C ++ ทั้งหมดทุกที่ทุกเวลา ไม่มีข้อยกเว้น.
Trevor Powell

3
@ TrevorPowell ฉันไม่ได้บอกว่าคุณผิด อันที่จริงฉันเห็นด้วยกับคุณ ฉันแค่บอกว่ามันไม่ได้ตอบคำถามที่ถาม มันเหมือนกับว่าคุณถามฉันว่า " ฉันจะทำให้เสียงเข้าสู่เกมของฉันได้อย่างไร " และคำตอบของฉันคือ "ฉันไม่อยากจะเชื่อเลยว่าคุณไม่มีเสียงใส่เสียงลงในเกมของคุณตอนนี้ " จากนั้นในวงเล็บ ใส่ " (คุณสามารถใช้ FMOD) " ซึ่งเป็นคำตอบที่แท้จริง
Tom Dalling

6
@TrevorPowell นี่คือที่ที่คุณผิด ไม่ใช่ "แค่มีระเบียบวินัย" ถ้าฉันไม่รู้จักทางเลือกอื่น ตัวอย่างรหัสที่ฉันให้ไว้เป็นทฤษฎีล้วนๆ ฉันรู้อยู่แล้วว่ามันเป็นการออกแบบที่ไม่ดี แต่ C ++ ของฉันนั้นเป็นสนิมดังนั้นฉันคิดว่าฉันจะถามเกี่ยวกับการออกแบบที่ดีกว่าก่อนที่ฉันจะเขียนโค้ดสิ่งที่ฉันต้องการ ดังนั้นฉันจึงมาถามเกี่ยวกับการออกแบบทางเลือก " เพิ่มการตั้งค่าสถานะการลบ " เป็นทางเลือก " ไม่ทำเลย " ไม่ใช่ทางเลือก มันแค่บอกสิ่งที่ฉันรู้แล้ว รู้สึกราวกับว่าคุณเขียนคำตอบโดยไม่อ่านคำถามอย่างถูกต้อง
Tom Dalling

4
@ บ๊อบบี้คำถามคือ "ทำอย่างไรถึงไม่ X" เพียงแค่พูดว่า "อย่าทำ X" เป็นคำตอบที่ไร้ค่า หากคำถามคือ "ฉันกำลังทำ X" หรือ "ฉันกำลังคิดที่จะทำ X" หรือตัวแปรอื่น ๆ จากนั้นก็จะได้พบกับพารามิเตอร์ของการอภิปรายเมตา แต่ไม่ใช่ในรูปแบบปัจจุบัน
Joshua Drake

21

แทนที่จะจัดคิวเหตุการณ์ในบัฟเฟอร์ให้จัดคิวการลบในบัฟเฟอร์ การลบแบบล่าช้ามีความเป็นไปได้ที่จะทำให้ตรรกะง่ายขึ้นอย่างมาก จริง ๆ แล้วคุณสามารถเพิ่มหน่วยความจำที่ท้ายหรือจุดเริ่มต้นของเฟรมเมื่อคุณรู้ว่าไม่มีอะไรน่าสนใจเกิดขึ้นกับวัตถุของคุณและลบออกจากที่ใดก็ได้


1
ตลกที่คุณพูดถึงเรื่องนี้เพราะฉันคิดว่าเป็นสิ่งที่ดีNSAutoreleasePoolจาก Objective-C จะอยู่ในสถานการณ์นี้ อาจต้องสร้างDeletionPoolเทมเพลต C ++ หรืออะไรบางอย่าง
Tom Dalling

@TomDalling สิ่งหนึ่งที่ต้องระวังเกี่ยวกับถ้าคุณทำบัฟเฟอร์ภายนอกกับวัตถุคือวัตถุอาจต้องการที่จะถูกลบด้วยเหตุผลหลายประการในกรอบเดียวและมันเป็นไปได้ที่จะพยายามลบมันหลายครั้ง
John Calsbeek

จริงแท้แน่นอน. ฉันจะต้องเก็บตัวชี้ไว้ใน std :: set
Tom Dalling

5
แทนที่จะเป็นบัฟเฟอร์ของวัตถุที่จะลบคุณสามารถตั้งค่าสถานะในวัตถุได้ เมื่อคุณเริ่มรู้ว่าคุณต้องการหลีกเลี่ยงการโทรใหม่หรือลบในระหว่างรันไทม์และย้ายไปยังกลุ่มวัตถุนั่นจะง่ายขึ้นและเร็วขึ้น
ฌอน Middleditch

4

แทนที่จะปล่อยให้โลกจัดการกับการลบคุณสามารถปล่อยให้อินสแตนซ์ของคลาสอื่นทำหน้าที่เป็นที่เก็บข้อมูลเพื่อเก็บเอนทิตีที่ถูกลบทั้งหมด อินสแตนซ์เฉพาะนี้ควรฟังENTITY_DEATHเหตุการณ์และจัดการกับมันเพื่อให้เข้าคิว Worldสามารถแล้วย้ำกว่ากรณีเหล่านี้และการดำเนินการหลังการตายหลังจากที่กรอบได้รับการแสดงผลและ 'ชัดเจน' ถังนี้ซึ่งจะเปิดดำเนินการลบที่เกิดขึ้นจริงของหน่วยงานอินสแตนซ์

ตัวอย่างของคลาสนั้นจะเป็นดังนี้: http://ideone.com/7Upza


+1 นั่นเป็นอีกทางเลือกหนึ่งในการตั้งค่าสถานะเอนทิตีโดยตรง เพิ่มเติมโดยตรงเพียงแค่มีชีวิตอยู่รายชื่อและรายการตายของหน่วยงานโดยตรงในWorldชั้นเรียน
Laurent Couvidou

2

ฉันขอแนะนำให้ติดตั้งโรงงานที่ใช้สำหรับการจัดสรรวัตถุทั้งหมดในเกม ดังนั้นแทนที่จะโทรหาตัวเองใหม่คุณจะบอกโรงงานให้สร้างบางอย่างให้คุณ

ตัวอย่างเช่น

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

เมื่อใดก็ตามที่คุณต้องการลบวัตถุโรงงานจะผลักวัตถุในบัฟเฟอร์ที่ล้างเฟรมถัดไป การทำลายที่ล่าช้ามีความสำคัญมากในสถานการณ์ส่วนใหญ่

พิจารณาการส่งข้อความทั้งหมดด้วยความล่าช้าหนึ่งเฟรม มีข้อยกเว้นเพียงไม่กี่ข้อเท่านั้นที่คุณต้องส่งทันทีกรณีส่วนใหญ่นั้นเป็นเพียงแค่


2

คุณสามารถใช้หน่วยความจำที่มีการจัดการใน C ++ ด้วยตัวคุณเองดังนั้นเมื่อENTITY_DEATHมีการเรียกใช้สิ่งที่เกิดขึ้นคือจำนวนการอ้างอิงของมันลดลงหนึ่งรายการ

ในภายหลังตาม @John แนะนำที่การขอร้องของทุกเฟรมคุณสามารถตรวจสอบว่าเอนทิตีใดที่ไร้ประโยชน์ (ที่มีการอ้างอิงเป็นศูนย์) และลบออก ตัวอย่างเช่นคุณสามารถใช้boost::shared_ptr<T>( เอกสารที่นี่ ) หรือถ้าคุณใช้ C ++ 11 (VC2010)std::tr1::shared_ptr<T>


เพียงแค่std::shared_ptr<T>ไม่ใช่รายงานทางเทคนิค! - คุณจะต้องระบุ deleter ที่กำหนดเองมิฉะนั้นจะลบวัตถุทันทีเมื่อจำนวนการอ้างอิงถึงศูนย์
leftaroundabout

1
@leftaroundabout มันขึ้นอยู่กับว่าอย่างน้อยฉันก็ต้องใช้ tr1 เป็น gcc แต่ใน VC ไม่จำเป็นต้องทำเช่นนั้น
Ali1S232

2

ใช้การรวมกำไรและอย่าลบวัตถุจริงๆ ให้เปลี่ยนโครงสร้างข้อมูลที่ลงทะเบียนแทน ตัวอย่างเช่นสำหรับการเรนเดอร์วัตถุมีวัตถุฉากและเอนทิตีทั้งหมดที่ลงทะเบียนสำหรับการเรนเดอร์การตรวจจับการชน ฯลฯ แทนการลบวัตถุให้แยกออกจากฉากและแทรกลงในพูลวัตถุที่ตายแล้ว วิธีนี้จะไม่เพียง แต่ป้องกันปัญหาหน่วยความจำ (เช่นวัตถุลบตัวเอง) แต่ยังอาจเพิ่มความเร็วในเกมของคุณหากคุณใช้สระว่ายน้ำอย่างถูกต้อง


1

สิ่งที่เราทำในเกมคือใช้ตำแหน่งใหม่

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

eventPool เป็นเพียงหน่วยความจำขนาดใหญ่ที่ถูกแกะสลักขึ้นและตัวชี้ไปยังแต่ละเซ็กเมนต์จะถูกเก็บไว้ ดังนั้น alloc () จะส่งคืนที่อยู่ของบล็อกหน่วยความจำว่าง ใน eventPool ของเราหน่วยความจำได้รับการปฏิบัติเหมือนเป็นสแต็คดังนั้นหลังจากเหตุการณ์ทั้งหมดถูกส่งไปเราก็แค่รีเซ็ตตัวชี้สแต็กกลับไปที่จุดเริ่มต้นของอาร์เรย์

เนื่องจากวิธีการที่ระบบจัดกิจกรรมของเราทำงานเราไม่จำเป็นต้องโทรหาผู้ทำลายล้างบนผ้านวม ดังนั้นกลุ่มจะลงทะเบียนบล็อกของหน่วยความจำว่าว่างและจะจัดสรร

สิ่งนี้ทำให้เรามีความเร็วมากขึ้น

นอกจากนี้ ... เราใช้พูลหน่วยความจำสำหรับการจัดสรรวัตถุแบบไดนามิกทั้งหมดในการพัฒนาเนื่องจากเป็นวิธีที่ดีในการค้นหาการรั่วไหลของหน่วยความจำหากมีวัตถุใด ๆ ที่เหลืออยู่ในกลุ่มเมื่อเกมออก (ปกติ) ก็เป็นไปได้ว่า การรั่วไหลของ mem

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.