Yazarken kopyalama semantiğinin avantajları


10

Yazarken kopyalamanın olası avantajlarının neler olduğunu merak ediyorum. Doğal olarak, kişisel görüşler beklemiyorum, ancak somut bir şekilde teknik ve pratik olarak faydalı olabileceği gerçek dünyadaki pratik senaryolar. Somut olarak, bir &karakteri yazmaktan daha fazlasını kastediyorum .

Açıklığa kavuşturmak için, bu soru, atama veya kopya oluşturma işleminin örtük bir sığ kopya oluşturduğu, ancak üzerinde yapılan değişikliklerin gizli bir derin kopya oluşturduğu ve değişiklikleri orijinal nesne yerine uyguladığı veri türleri bağlamındadır.

Sormamın nedeni, varsayılan örtük davranış olarak COW'a sahip olmanın herhangi bir faydasını bulamıyorum. Birçok veri türü için COW uygulanmış olan Qt, neredeyse hepsinin altında dinamik olarak ayrılmış depolama alanı var. Peki kullanıcıya gerçekten nasıl fayda sağlar?

Bir örnek:

QString s("some text");
QString s1 = s; // now both s and s1 internally use the same resource

qDebug() << s1; // const operation, nothing changes
s1[o] = z; // s1 "detaches" from s, allocates new storage and modifies first character
           // s is still "some text"

Bu örnekte COW kullanarak ne kazanıyoruz?

Yapmak istediğimiz tek şey const işlemlerini kullanmaksa s1gereksizdir, kullanmak da mümkündür s.

Değeri değiştirmek niyetindeysek, COW, kaynak kopyayı yalnızca ilk sabit olmayan işleme kadar, örtülü paylaşım için ref sayımını arttırma ve paylaşılan depolama biriminden ayırma maliyetini (minimum da olsa) erteler. COW ile ilgili tüm ek yükün anlamsız olduğu anlaşılıyor.

Parametre geçirme bağlamında çok farklı değildir - değeri değiştirmek istemiyorsanız, const referansı olarak geçin, değiştirmek istiyorsanız, değiştirmek istemiyorsanız örtük bir derin kopya yaparsınız orijinal nesneyi veya değiştirmek istiyorsanız referans ile iletin. Yine COW, hiçbir şey elde etmeyen gereksiz bir ek yük gibi görünüyor ve herhangi bir değişiklik orijinal nesneden ayrılacağından, istediğinizde bile orijinal değeri değiştiremeyeceğiniz bir sınırlama ekliyor.

Bu nedenle, COW hakkında bilgi sahibi olmanıza veya habersiz olmanıza bağlı olarak, ya belirsiz niyet ve gereksiz yük ile kodla sonuçlanabilir veya beklentileri karşılamayan ve başınızı çizmenize neden olan tamamen kafa karıştırıcı bir davranışla sonuçlanabilir.

Bana göre, gereksiz derin bir kopyadan kaçınmak isteyip istemediğiniz veya daha fazlasını yapmak isteyip istemediğiniz daha verimli ve daha okunabilir çözümler var gibi görünüyor. Peki COW'un pratik yararı nerededir? Bu kadar popüler ve güçlü bir çerçevede kullanıldığı için bazı faydalar olduğunu varsayıyorum.

Ayrıca, okuduğum kadarıyla, COW artık C ++ standart kütüphanesinde açıkça yasaklanmıştır. İçinde gördüğüm con'ların bununla bir ilgisi olup olmadığını bilmiyorum, ama her iki durumda da bunun bir nedeni olmalı.

Yanıtlar:


15

Yazarken kopyala, nesnenin bir kopyasını çok sık oluşturacağınız ve değiştirmeyeceğiniz durumlarda kullanılır. Bu durumlarda kendini amorti eder.

Bahsettiğiniz gibi, bir const nesnesini iletebilirsiniz ve çoğu durumda bu yeterlidir. Bununla birlikte, const sadece arayan kişinin onu değiştiremeyeceğini garanti eder const_cast(tabii ki eğer değilse ). Çok iş parçacıklı vakaları işlemez ve geri çağrıların olduğu (orijinal nesneyi değiştirebilir) vakaları işlemez. Bir COW nesnesinin değere göre iletilmesi, bu ayrıntıların API kullanıcısı yerine API geliştiricisinde yönetilmesinin zorluğunu ortaya çıkarır.

C + 11 için yeni kurallar std::stringözellikle COW'u yasaklamaktadır . Destek arabelleği ayrılırsa, bir dizedeki yineleyiciler geçersiz kılınmalıdır. Yineleyici char*(a string*ve dizinin aksine ) olarak uygulanıyorsa, bu yineleyiciler artık geçerli değildir. C ++ topluluğu yineleyicilerin ne sıklıkta geçersiz kılınabileceğine karar vermek zorundaydı ve karar operator[], bu durumlardan biri olmamalıdır. operator[]değiştirilebilen a std::stringdöndürür char&. Bu nedenle, operator[]yineleyicileri geçersiz kılan dizeyi ayırmanız gerekir. Bu kötü bir ticaret olarak kabul ve benzeri fonksiyonların aksine edildi end()ve cend()ait const versiyonu için sormak yolu yoktur operator[]dize döküm Kat kısa. ( ilgili ).

COW hala hayatta ve STL'nin çok dışında. Özellikle, API'larımın bir kullanıcının çok hafif bir nesnenin arkasında ağır bir nesne olmasını beklemesinin mantıklı olmadığı durumlarda çok yararlı buldum. Bu tür uygulama ayrıntıları ile asla ilgilenmemelerini sağlamak için COW'u arka planda kullanmak isteyebilirim.


Aynı dizeyi birden çok iş parçacığında değiştirmek, yineleyicileri veya []operatörü kullansanız da çok kötü bir tasarım gibi görünür . Yani COW kötü tasarıma izin veriyor - bu pek bir faydası yok gibi görünüyor :) Son paragraftaki nokta geçerli görünüyor, ama ben kendimi örtük davranışların büyük bir hayranı değilim - insanlar bunu kabul etmek için eğilimlidirler ve sonra kodun neden beklendiği gibi çalışmadığını anlamak zor bir zaman ve örtük davranışın arkasında neyin gizlendiğini kontrol edene kadar merak etmeye devam edin.
dtech

Kullanım noktasına gelince, const_castCOW'yu const referansı ile geçebildiği kadar kolay kırabilecek gibi görünüyor. Örneğin, QString::constData()bir const QChar *- const_castthat döndürür ve COW daraltır - orijinal nesnenin verilerini değiştirirsiniz.
dtech

Bir COW'dan veri döndürebiliyorsanız, bunu yapmadan önce ayırmanız veya verileri hala COW farkında olan bir formda döndürmeniz gerekir ( char*açıkça farkında değildir). Örtük davranışa gelince, haklı olduğunu düşünüyorum, bununla ilgili sorunlar var. API tasarımı iki uç arasında sabit bir dengedir. Çok örtük ve insanlar sanki şartnamenin fiili bir parçasıymış gibi özel davranışlara güvenmeye başlıyorlar. Çok açık ve çok önemli olmayan ve aniden API spesifikasyonunuza yazılan çok sayıda temel ayrıntıyı ortaya koyduğunuzda API çok hantal hale geliyor.
Cort Ammon

stringDerleyici tasarımcıları büyük bir kod gövdesi const başvuru kullanmak yerine dizeleri kopyalama olduğunu fark çünkü sınıfları COW davranış var inanıyorum . COW eklediyse, bu davayı optimize edebilir ve daha fazla insanı mutlu edebilirler (ve C ++ 11'e kadar yasaldı). Konumlarını takdir ediyorum: Dizelerimi her zaman const referansıyla geçirirken, okunabilirlikten uzaklaşan tüm sözdizimsel önemsiz şeyleri görüyordum. const std::shared_ptr<const std::string>&Doğru semantiği yakalamak için yazmaktan nefret ediyorum !
Cort Ammon

5

Dizeler ve bu tür için, dizeler için ortak durum genellikle küçük dizeler olduğundan ve orada COW yükü küçük dizeyi kopyalama maliyetinden çok daha ağır basma eğiliminde olduğundan daha yaygın kullanım durumlarını kötüleştirecek gibi görünüyor. Bu gibi durumlarda dize kopyaları yerine yığın tahsisini önlemek için küçük bir arabellek optimizasyonu bana çok daha mantıklı geliyor.

Bununla birlikte, bir android gibi daha hevesli bir nesneniz varsa ve onu kopyalamak ve sadece sibernetik kolunu değiştirmek istiyorsanız, COW, tüm android'i derinlemesine kopyalama ihtiyacını ortadan kaldırırken değişebilir bir sözdizimi tutmanın bir yolu olarak oldukça makul görünüyor. kopyaya benzersiz bir kol verin. Bu noktada kalıcı bir veri yapısı olarak değişmez hale getirmek daha üstün olabilir, ancak bireysel android parçalara uygulanan "kısmi bir COW" bu durumlar için makul görünmektedir.

Böyle bir durumda, android'in iki kopyası aynı gövde, bacaklar, ayaklar, baş, boyun, omuzlar, pelvis vb. kolunun üzerine ikinci android için benzersiz.


Bunların hepsi iyi, ama COW talep etmiyor ve hala çok zararlı etkilere maruz kalıyor. Ayrıca, bunun bir dezavantajı var - genellikle nesne örnekleme yapmak isteyebilirsiniz ve tip örnekleme demek istemiyorum, ancak bir nesneyi örnek olarak kopyalamak, böylece kaynak nesneyi değiştirdiğinizde kopyalar da güncellenir. "Paylaşılan" bir nesnede yapılan herhangi bir değişiklik onu ayırdığından, COW bu olasılığı dışlar.
dtech

Doğruluk IMO'nun başarılması “kolay” olmamalı, örtük davranışlarla değil. Doğruluğun iyi bir örneği, açık olduğu ve belirsizlikler veya görünmez yan etkilere yer bırakmadığı için CONST doğruluğudur. Bu "kolay" ve otomatik gibi bir şeye sahip olmak asla, şeylerin nasıl çalıştığına dair ekstra bir anlayış düzeyi oluşturmaz, bu da sadece genel üretkenlik için önemli değildir, aynı zamanda istenmeyen davranış olasılığını neredeyse ortadan kaldırır; . COW ile dolaylı olarak mümkün olan her şeyin açık bir şekilde elde edilmesi de kolaydır ve daha açıktır.
dtech

Sorum, çalıştığım dilde varsayılan olarak COW sağlayıp sağlamayacağı bir ikilemle motive oldu. Pro ve con'ları ağırlıklandırdıktan sonra, varsayılan olarak değil, hem yeni hem de mevcut türlere uygulanabilen bir değiştirici olarak karar verdim. Her iki dünyanın en iyisi gibi görünüyor, hala istemek konusunda açık olduğunuzda COW'un örtüklüğüne sahip olabilirsiniz.
dtech

@ddriver Nod paradigma ile bir programlama diline benzeyen bir şey var, basitlik dışında düğümler kullanım değeri semantiği ve referans tipi semantik yok (belki C ++ 11'de semantikten std::vector<std::string>önce biraz benziyor emplace_backve anlambiliyor) . Ama temelde örnekleme kullanıyoruz. Düğüm sistemi verileri değiştirebilir veya değiştirmeyebilir. Girdi ile hiçbir şey yapmayan ancak sadece bir kopyasını çıkaran geçiş düğümleri gibi şeylere sahibiz (programının kullanıcı organizasyonu için oradalar). Bu durumlarda, tüm veriler karmaşık tipler için sığ kopyalanır ...

@ddriver Yazarken kopyalamamız etkin bir şekilde "örneği dolaylı olarak değiştiğinde benzersiz yap" türünde bir kopyalama işlemidir. Orijinali değiştirmeyi imkansız hale getirir. Nesne Akopyalanırsa ve nesneye hiçbir şey yapılmazsa B, kafesler gibi karmaşık veri türleri için ucuz sığ bir kopyadır. Şimdi değiştirirsek B, değiştirdiğimiz veriler BCOW yoluyla benzersiz hale gelir, ancak Adokunulmaz (bazı atomik referans sayıları hariç).
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.