Oyun nesnelerinin yanlışlıkla C ++ ile silinmesi nasıl önlenir


20

Diyelim ki oyunumun kamikaze üzerinde oyuncu patlayabilir bir canavar var. Bu canavar için rastgele bir isim seçelim: bir Creeper. Yani, Creepersınıfın şuna benzer bir yöntemi vardır:

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

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

Olaylar sıraya alınmaz, hemen gönderilir. Bu, Creepernesnenin çağrının içinde bir yerde silinmesine neden olur postEvent. Bunun gibi bir şey:

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

Çünkü Creeperise nesne silinir kamikazeyöntemi hala çalışıyorsa o erişmeye çalıştığında, bu çökmesine olacak this->location().

Bir çözüm, olayları bir arabellekte sıralamak ve daha sonra göndermektir. Bu C ++ oyunlarında ortak çözüm mü? Biraz hack gibi geliyor, ancak bunun nedeni sadece farklı bellek yönetimi uygulamalarına sahip diğer dillerle yaşadığım deneyim olabilir.

C ++ 'da, bir nesnenin kendi yöntemlerinden birinin içinden yanlışlıkla sildiği bu soruna daha iyi bir genel çözüm var mı?


6
uh, başlangıç ​​yerine kamikaze yönteminin sonundaki postEvent'i çağırmaya ne dersiniz?
Hackworth

@Hackworth bu özel örnek için işe yarar, ama daha genel bir çözüm arıyorum. Olayları her yerden gönderebilmek ve çökmelere neden olmaktan korkmamak istiyorum.
Tom Dalling

Ayrıca autoreleasesilme işlemlerinin "biraz" yapılmasına kadar devam ettiği Objective-C uygulamasına da göz atabilirsiniz .
Chris Burt-Brown

Yanıtlar:


40

Silme this

Örtük olarak bile.

- Hiç -

Üye işlevlerinden biri hala yığıntayken bir nesneyi silmek sorun için yalvarıyor. Bu ("yanlışlıkla" ya da değil) ile sonuçlanan herhangi bir kod mimarisi objektif olarak kötüdür , tehlikelidir ve derhal yeniden düzenlenmesi gerekir . Bu durumda, canavarınızın 'World :: handleEvent' adını vermesine izin verilecekse , hiçbir koşulda bu işlevin içindeki canavarı silmeyin!

(Bu özel durumda, her zamanki yaklaşımım canavarın kendi üzerine 'ölü' bir bayrak koymasını ve Dünya nesnesinin - ya da onun gibi bir şeyin - çerçeve başına bir kez 'ölü' bayrağı test etmesini ve bu nesneleri kaldırmasını sağlamaktır. dünyadaki nesneler listesinden ya da onu silme ya da bir canavar havuzuna ya da uygun olan her şeye geri döndürme.Bu zamanda dünya aynı zamanda silme hakkında bildirimler gönderir, böylece dünyadaki diğer nesneler canavarın mevcut durdu ve tutuyor olabilecekleri herhangi bir işaretçi bırakabilir.Dünya, bunu şu anda hiçbir nesnenin işlemediğini bildiğinde güvenli bir zamanda yapar, böylece bir noktaya gevşeyen yığın hakkında endişelenmenize gerek kalmaz burada 'bu' işaretçi serbest hafızaya işaret eder.)


6
Parantez içindeki cevabınızın ikinci yarısı yardımcı oldu. Bir bayrak için oy kullanmak iyi bir çözümdür. Ama bunu yapmaktan nasıl kaçınacağımı soruyordum, bunun iyi ya da kötü bir şey olup olmadığını değil. Bir soru sorarsa " Nasıl? Yanlışlıkla X yapıyor önleyebilirsiniz " ve cevabınız "olduğunu bile yanlışlıkla, şimdiye X asla yapmayın aslında soruya cevap etmediğini, kalın".
Tom Dalling

7
Cevabımın ilk yarısında yaptığım yorumların arkasında duruyorum ve soruya orijinal olarak ifade edildiği gibi tamamen cevap verdiklerini hissediyorum. Burada tekrarlayacağım önemli nokta, bir nesnenin kendisini silmemesidir. Hiç . Silmek için başka birini çağırmaz. Hiç . Bunun yerine, nesnenin sahibi olan ve nesnenin ne zaman yok edilmesi gerektiğini fark etmekten sorumlu olan, nesnenin dışında başka bir şeye sahip olmanız gerekir. Bu "bir canavar öldüğünde" bir şey değildir; Bu her zaman, her yerde, her zaman, tüm C ++ kodu içindir. İstisna yok.
Trevor Powell

3
@TrevorPowell Yanlış olduğunu söylemiyorum. Aslında, sana katılıyorum. Sadece sorulan soruya cevap vermediğini söylüyorum. Bana " Sesi oyunuma nasıl eklerim? " Diye sordum ve cevabım " Sese sahip olmadığına inanamıyorum. Şu anda oyununa ses koy. " Sonra alt kısımda parantez içinde gerçek bir cevap olan " (FMOD kullanabilirsiniz) " ifadesini kullanın .
Tom Dalling

6
@TrevorPowell Yanlış yaptığınız yer burası. Başka alternatif bilmiyorsam "sadece disiplin" değildir. Verdiğim örnek kod tamamen teorik. Zaten kötü bir tasarım olduğunu biliyorum, ama benim C ++ paslı, bu yüzden aslında ne istediğimi kodlamak önce burada daha iyi tasarımlar hakkında sormak düşündüm. Böylece alternatif tasarımlar hakkında soru sormaya geldim. " Silme bayrağı ekleme " bir alternatiftir. " Asla yapma " bir alternatif değildir . Bana zaten ne bildiğimi söylüyor. Soruyu doğru okumadan cevabı yazmışsınız gibi geliyor.
Tom Dalling

4
@Bobby Soru "X nasıl YAPILMAZ" sorusudur. "X YAPMAYIN" demek değersiz bir cevaptır. Eğer soru "X yapıyordum" ya da "X yapmayı düşünüyordum" ya da bunun herhangi bir çeşidi olsaydı, meta tartışmanın parametrelerini karşılayacaktı, ama mevcut haliyle değil.
Joshua Drake

21

Bir arabellekteki olayları kuyruğa almak yerine, arabellekteki silme işlemlerini sıraya alın . Gecikmeli silme, mantığı büyük ölçüde basitleştirme potansiyeline sahiptir; nesnelerinize ilginç bir şey olmadığını bildiğinizde bir çerçevenin sonunda veya başında belleği boşaltabilir ve kesinlikle her yerden silebilirsiniz.


1
Komik, bundan bahsettin, çünkü NSAutoreleasePoolObjective-C'den bu durumda ne kadar güzel olacağını düşünüyordum. DeletionPoolC ++ şablonlarıyla falan yapmak gerekebilir .
Tom Dalling

@TomDalling Arabelleği nesneye harici yaparsanız dikkat etmeniz gereken tek şey, bir nesnenin tek bir karede birden çok nedenden dolayı silinmek isteyebileceğidir ve bunu birkaç kez silmeyi mümkün kılmaktır.
John Calsbeek

Çok doğru. Ben işaretçiler bir std :: set tutmak zorunda kalacak.
Tom Dalling

5
Silinecek nesnelerin tamponundan ziyade, nesnede bir bayrak da ayarlayabilirsiniz. Çalışma zamanında yeni çağrı yapmaktan veya silmekten kaçınmak istediğinizi fark etmeye başladığınızda ve bir nesne havuzuna taşındığınızda, bu hem daha basit hem de daha hızlı olacaktır.
Sean Middleditch

4

Dünyanın, silme işlemini gerçekleştirmesine izin vermek yerine, başka bir sınıf örneğinin silinen tüm varlıkları depolamak için bir grup görevi görmesine izin verebilirsiniz. Bu örnek ENTITY_DEATHolayları dinlemeli ve bunları sıraya alacak şekilde işlemelidir. Daha Worldsonra kutu bu örnekler üzerinde yinelenir ve çerçeve oluşturulduktan sonra ölüm sonrası işlemleri gerçekleştirir ve bu kovayı 'temizler', bu da varlık örneklerinin gerçek silinmesini gerçekleştirir.

Böyle bir sınıfın örneği şöyle olabilir: http://ideone.com/7Upza


+1, bu varlıkların doğrudan işaretlenmesine bir alternatiftir. Daha doğrudan, sadece Worldsınıfta bir canlı liste ve varlıkların bir ölü listesi var .
Laurent Couvidou

2

Oyundaki tüm oyun nesnesi tahsisleri için kullanılan bir fabrika kurmanızı öneririm. Bu yüzden, kendinizi yeni aramak yerine, fabrikaya sizin için bir şeyler yaratmasını söylersiniz.

Örneğin

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

Bir nesneyi silmek istediğinizde, fabrika nesneyi sonraki kareyi temizleyen bir arabellek içine iter. Gecikmiş yıkım çoğu senaryoda çok önemlidir.

Ayrıca, tüm mesajları bir kare gecikmeyle göndermeyi düşünün. Hemen göndermeniz gereken birkaç istisna vardır, ancak vakaların büyük çoğunluğu


2

Yönetilen belleği C ++ 'da kendiniz uygulayabilirsiniz, böylece ENTITY_DEATHçağrıldığında, gerçekleşen tek şey referanslarının sayısı bir azalır.

Daha sonra @John'un her karenin dilenmesinde önerdiği gibi, hangi varlıkların yararsız olduğunu (sıfır referanslı olanlar) kontrol edebilir ve silebilirsiniz. Örneğin boost::shared_ptr<T>( burada belgelenmiş ) kullanabilirsiniz veya C ++ 11 (VC2010) kullanıyorsanızstd::tr1::shared_ptr<T>


Sadece std::shared_ptr<T>teknik değil raporlar! - Özel bir silme belirtmeniz gerekir, aksi takdirde referans sayısı sıfıra ulaştığında nesneyi de siler.
leftaroundabout

1
@leftaroundabout gerçekten bağlıdır, en azından gcc'de tr1 kullanmam gerekiyordu. fakat VC'de buna gerek yoktu.
Ali1S232

2

Havuzu kullanın ve nesneleri gerçekten silmeyin. Bunun yerine, kayıtlı oldukları veri yapısını değiştirin. Örneğin, nesneleri oluşturmak için bir sahne nesnesi ve oluşturma, çarpışma tespiti vb. İçin bir şekilde ona kaydedilmiş olan tüm varlıklar vardır. Bu yöntem yalnızca bellek sorunlarını (örneğin kendini silen bir nesne) önlemekle kalmaz, aynı zamanda havuzu doğru kullanırsanız oyununuzu hızlandırabilir.


1

Bir oyunda yaptığımız yeni yerleştirme kullanmaktı

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

eventPool, oyulmuş büyük bir bellek dizisiydi ve her bir segmentin işaretçisi saklandı. Böylece ,landıran () boş bir bellek bloğunun adresini döndürür. EventPool'umuzda bellek bir yığın olarak değerlendirildi, bu yüzden tüm olaylar gönderildikten sonra yığın işaretçisini dizinin başlangıcına sıfırlayacağız.

Etkinlik sistemimizin çalışma şekli nedeniyle evet'lerdeki yıkıcıları çağırmamız gerekmiyordu. Böylece havuz basitçe bellek bloğunu ücretsiz olarak kaydeder ve tahsis eder.

Bu bize büyük bir hız kazandırdı.

Ayrıca ... Aslında, bellek sızıntılarını bulmanın harika bir yolu olduğu için, oyun sırasında çıkıldığında (normalde) havuzlarda kalan herhangi bir nesne varsa, o zaman muhtemelen bir mem sızıntısı.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.