Peki bir sınıf hangi noktada değişmez olamayacak kadar karmaşık hale geliyor?
Bence, küçük sınıfları gösterdiğiniz diller gibi değişmez kılmak zahmete değmez . Burada küçük kullanıyorum, karmaşık değil , çünkü o sınıfa on alan ekleseniz ve gerçekten süslü operasyonlar yapsa bile, megabaytların gigabaytları bırakmasına izin vermeden kilobayt alacağından şüpheliyim, sınıf, harici yan etkilere neden olmaktan kaçınmak istiyorsa orijinali değiştirmekten kaçınmak için tüm nesnenin ucuz bir kopyasını oluşturabilir.
Kalıcı Veri Yapıları
Değişmezlik için kişisel kullanım bulduğum yerde, gösterdiğiniz sınıf örnekleri gibi, bir milyonu depolayan gibi bir sürü ufacık veri toplayan büyük, merkezi veri yapıları içindir NamedThings
. Değişmez olan ve sadece salt okunur erişime izin veren bir arayüzün arkasında yer alan kalıcı bir veri yapısına ait olarak, kaba ait elemanlar, element sınıfı ( NamedThing
) ile uğraşmak zorunda kalmadan değişmez hale gelir .
Ucuz Kopyalar
Kalıcı veri yapısı, bölgelerinin dönüştürülmesine ve benzersiz hale getirilmesine izin vererek, veri yapısının tamamını kopyalamak zorunda kalmadan orijinalde değişiklik yapılmasını önler. Bu onun gerçek güzelliği. Gigabayt bellek alan ve sadece bir megabaytın belleğini değiştiren bir veri yapısı giren yan etkilerden kaçınan işlevler yazmak istiyorsanız, girişe dokunmaktan kaçınmak ve yenisini döndürmek için tüm hilkat garibesini kopyalamanız gerekir. çıktı. Yan etkilerden kaçınmak için gigabaytları kopyalamak veya bu senaryoda yan etkilere neden olmak, iki hoş olmayan seçenek arasında seçim yapmanız gerektiğini gösterir.
Kalıcı bir veri yapısı ile, böyle bir işlevi yazmanıza ve tüm veri yapısının bir kopyasını yapmaktan kaçınmanıza izin verir, yalnızca işleviniz bir megabaytın belleğini dönüştürdüyse, çıktı için yalnızca bir megabayt ekstra bellek gerektirir.
Sorumluluk
Yüke gelince, en azından benim durumumda hemen bir tane var. Bu büyük veri yapısına dokunmadan dönüşümleri etkili bir şekilde ifade edebilmek için onları çağırdığımda, insanların oluşturduğu veya "geçici" olan inşaatçılara ihtiyacım var. Bunun gibi kod:
void transform_stuff(MutList<Stuff>& stuff, int first, int last)
{
// Transform stuff in the range, [first, last).
for (; first != last; ++first)
transform(stuff[first]);
}
... o zaman şöyle yazılmalıdır:
ImmList<Stuff> transform_stuff(ImmList<Stuff> stuff, int first, int last)
{
// Grab a "transient" (builder) list we can modify:
TransientList<Stuff> transient(stuff);
// Transform stuff in the range, [first, last)
// for the transient list.
for (; first != last; ++first)
transform(transient[first]);
// Commit the modifications to get and return a new
// immutable list.
return stuff.commit(transient);
}
Ancak bu iki ekstra kod satırı karşılığında, işlev artık aynı orijinal listeye sahip iş parçacıkları arasında arama yapmak güvenlidir, yan etkilere neden olmaz, vb. Ayrıca, bu işlemi geri alınamaz bir kullanıcı eylemi haline getirmeyi gerçekten kolaylaştırır. geri almak sadece eski listenin ucuz bir sığ kopyasını saklayabilir.
İstisna Güvenliği veya Hata Kurtarma
Herkes bu gibi bağlamlarda kalıcı veri yapılarından benim kadar faydalanamayabilir (VFX alanımda merkezi kavramlar olan geri alma sistemlerinde ve tahribatsız düzenlemede onlar için çok fazla kullanım buldum), ancak hemen hemen uygulanabilir bir şey göz önünde bulundurulması gereken herkes istisna güvenliği veya hata kurtarmadır .
Orijinal mutasyon işlevini istisna-güvenli hale getirmek istiyorsanız, en basit uygulamanın tüm listeyi kopyalamasını gerektiren geri alma mantığına ihtiyacı vardır :
void transform_stuff(MutList<Stuff>& stuff, int first, int last)
{
// Make a copy of the whole massive gigabyte-sized list
// in case we encounter an exception and need to rollback
// changes.
MutList<Stuff> old_stuff = stuff;
try
{
// Transform stuff in the range, [first, last).
for (; first != last; ++first)
transform(stuff[first]);
}
catch (...)
{
// If the operation failed and ran into an exception,
// swap the original list with the one we modified
// to "undo" our changes.
stuff.swap(old_stuff);
throw;
}
}
Bu noktada, istisna-güvenli mutable versiyonu bir "oluşturucu" kullanarak değişmez versiyondan daha hesaplamalı olarak pahalı ve tartışmasız daha da zor. Ve bir çok C ++ geliştiricisi sadece istisna güvenliği ihmal ediyor ve belki de bu onların etki alanı için iyi, ancak benim durumumda, bir istisna durumunda bile kodumun doğru çalıştığından emin olmak istiyorum (istisnaları test etmek için kasıtlı olarak istisnalar atan testler bile yazmayı seviyorum) güvenlik), ve bu da bir işlevin neden olduğu herhangi bir yan etkiyi yarıya indirebilmem gerekiyor.
İstisna güvende olmak ve uygulamanız çökmeden ve yanmadan hatalardan incelikle kurtulmak istediğinizde, bir fonksiyonun bir hata / istisna durumunda neden olabileceği herhangi bir yan etkiyi geri almanız / geri almanız gerekir. Ve orada inşaatçı aslında hesaplama zamanıyla birlikte maliyetinden daha fazla programcı zaman kazandırabilir, çünkü: ...
Herhangi bir şeye neden olmayan bir fonksiyonda yan etkileri geri almak konusunda endişelenmenize gerek yok!
Temel soruya dönelim:
Değişmez sınıflar hangi noktada yük haline gelir?
Onlar her zaman değişebilirlik etrafında değişmezlikten daha fazla dönen dilde bir yüktür, bu yüzden faydaların maliyetlerden önemli ölçüde ağır bastığı yerlerde bunları kullanmalısınız. Ancak yeterince büyük veri yapıları için yeterince geniş bir seviyede, bunun değerli bir takas olduğu birçok durum olduğuna inanıyorum.
Ayrıca benimkinde, sadece birkaç değişmez veri türüm var ve bunların hepsi çok sayıda öğe (bir görüntünün / dokunun pikselleri, bir ECS'nin objeleri ve bileşenleri) ve köşeleri / kenarları / çokgenleri bir ağ).