Kod çoğaltma C gerekli bir kötülük mü?


16

C için oldukça yeniyim ve genel veri yapıları ve genel olarak C yazma söz konusu olduğunda kod çoğaltmanın gerekli bir kötülük olup olmadığını merak ediyorum?

hash mapÖrneğin genel bir uygulama yazmaya çalışabilirim , ama her zaman sonuçların dağınık olduğunu düşünüyorum. Ayrıca, sadece bu özel kullanım durumu için özel bir uygulama yazabilir, kodu açık ve okunması ve hata ayıklaması kolay tutabilirim. İkincisi elbette bazı kodların çoğaltılmasına yol açacaktır.

Genel uygulamalar bir norm mu yoksa her kullanım durumu için farklı uygulamalar mı yazıyorsunuz?


11
Açıkladığınız ahlaki tartışma C'ye özgü değildir. Anekdot olarak, genel bir uygulama oluşturma çubuğunun, kitleniz yoksa, oldukça yüksek olduğunu düşünüyorum. İyi bir genel uygulama oluşturmak için gereken çaba, nokta çözüm olan IME'yi aşar.
Robert Harvey


1
@kevincline: Java'nın jenerikleri var. Verilen, jeneriklerin "perdenin arkasındaki adama dikkat etme" türü.
Robert Harvey

1
Ve jeneriklerden önce, Object'e geçip sırasıyla Float, Double, Integer ve Long'a geçebilirsiniz.
Daniel Kaplan

3
Deja vu bir kod kokusu kontrolüdür ve DRY için tercih IMO'ya dikkat etmeye değer en iyi uygulamaların kökenindedir. Ama kendimi tekrarlamaktan kaçınmaya çalışarak OOP odaklı dillerde ayağa vurdum. İlişkisiz endişeleri gereksiz yere birbirine bağlamıyorsanız veya iki benzer fonksiyonun biri olması için okunaklılığı yok etmiyorsanız, içgüdüye çoğu dilde giderim.
Erik Reppen

Yanıtlar:


27

C, genel kod yazmayı zorlaştırır. Size şablonlar ve sanal işlevler sağlayan C ++ 'dan farklı olarak, C'nin genel kod yazmak için yalnızca 3 mekanizması vardır:

  1. void* işaretçileri
  2. Önişlemci makroları
  3. İşlev işaretçileri

void* işaretçiler ideal olmaktan uzaktır, çünkü derleyici tarafından sağlanan tüm güvenliği kaybedersiniz, bu da geçersiz tür dökümlerinden kaynaklanan hata ayıklaması zor tanımlanmamış davranışlara neden olabilir.

Önişlemci makrolarının iyi bilinen dezavantajları vardır - önişlemci genişletmesi temel olarak derleme aşamasından önce gerçekleşen bir bul / değiştir mekanizmasıdır ve bu da hata ayıklaması zor hatalara neden olabilir. Gibi arketip örnek olmak bir şey: #define add(x) (x+x)nerede xAraman iki kez artırılabilir add(i++). Sen edebilir tamamen C-makroları kullanarak şablon tarzı jenerik kod yazmak, ancak sonuç gerçekten iğrenç ve bakımı zor .

İşlev işaretçileri genel kod yazmak için iyi bir yol sağlar, ancak maalesef size tip genelliği sağlamazlar - sadece çalışma zamanı polimorfizmi sağlarlar (bu nedenle, örneğin, standart kütüphane qsorthala bir işlevi gerektirir void*işaretçileri.)

Ayrıca, sınıf temel hiyerarşilerini, genel bir GObjecttemel sınıf sağlayan GLib kitaplığında olduğu gibi, yapıları kullanarak da uygulayabilirsiniz . Ancak bu void*, işaretçiler kullanma ile benzer sorunlardan muzdariptir , çünkü hala dökme ve aşağı dökme için potansiyel olarak güvenli olmayan manuel dökümlere güvenmeniz gerekir.

Evet, C, maalesef kod çoğaltmasıyla sonuçlanabilecek genel VE güvenli / bakımı kolay kod yazmayı zorlaştırıyor. Büyük C projeleri, oluşturma işlemi sırasında yinelenen kod oluşturmak için genellikle komut dosyası dillerini kullanır.


1
Ayrıca, harici şablon diller / araçlar veya bir kabuk komut dosyası kullanarak veya karınca veya kullanarak tekrarlayan C kodu üretebilir ...
Job

Yanıtınıza verdiğiniz Google tarafından gizlenen bu Rus URL'si nedir? Üzerine tıklamak tarayıcımda "açık" bir istemin görünmesine neden oluyor. Bu güvenli mi?
Robert Harvey

2
@RobertHarvey Virüsten koruma ve güvenlik taramalarım bunun iyi olduğunu düşünüyor. Bir C dili Başlık dosyası, uzantısı .h. Yasal görünüyor.
maple_shaft

@ maple_shaft: Tamam. Bağlantının çevresindeki Google'ı kaldırdım.
Robert Harvey

Sadece düz metinli bir .h (C başlığı) dosyası
Charles Salvia

15

Başkaları için konuşamam, ama C ile kendi kişisel deneyimimde, kod çoğaltma çok sorun değildi. Bunun proje boyutlarından mı, yoksa büyülenmiş bir örneklemden mi kaynaklandığını söyleyemem. Ancak, geçerli olduğunu düşündüğüm üç temel kural vardır. Belirli bir sırayla, onlar ...

  1. İhtiyacınız olan şey için yazın. Gerekirse genellik daha sonra gelebilir.
  2. Genelliğe ihtiyaç duyulursa, boş işaretçiler, işlev işaretçileri ve yapı boyutu çok değerli olabilir. Örneğin rutin qsort (), üçünün tümünü kullanır.
  3. Kodunuzu açıklığa kavuşturmaya özen gösterin.

5

Kod çoğaltmayı azaltmak için tamamen genel bir çözüme ihtiyacınız olmayabileceğini belirtmek gerekir. Bazen sıradan yeniden düzenleme ve biraz yöntem genellemesi yeterlidir.

Geniş bir kitle için genelleştirilmiş bir çözüm yazarken şunları göz önünde bulundurmalısınız:

  1. Tüketici kodunuzu hangi yeni yöntemleri kullanabilir ve bunları nasıl kullanabilirsiniz?
  2. Hangi hataları yakalamam gerekiyor? Ne hatalar gerektiğini değil yakalamak?
  3. API'nın ne kadar sağlam olması gerekir? Kaç tane aşırı yük sağlamam gerekiyor?
  4. Kodun kötü amaçlar için kullanılamaması için hangi güvenlik önlemlerini almam gerekiyor?

Eric Lippert , .NET çerçeve kodunda tek bir tasarım kararı hakkında bir makale yazdı . Sonuçta, sadece refactor için genellikle daha basittir.


3

Genellikle glib gibi genel bir uygulama kullanırım , eğer döküm çok sinir bozucu olursa, bunun için küçük bir türe özgü sarıcı yapın. Bununla birlikte, void*genel bir tür olarak a'nın kullanımı gibi C'de çok fazla döküm bekleniyor , bu yüzden başka bir dilde "dağınık" olarak kabul edilecek şey sadece tipik C'dir. Ne kadar ki, çok daha doğal görünecek dilde daha fazla deneyim kazanın.


2
"Başka bir dilde 'dağınık' olarak kabul edilecek şey sadece tipik bir C. Sadece yeterince, dil konusunda daha fazla deneyim kazandığınızda çok daha doğal görünecek." Ha, ha, ha! C her zaman en sevdiğim dillerden biri oldu, ama yine de oldukça komik!
GlenPeterson

0

Yeniden kullanılabilirlik konusunuz C'ye özgü değildir. Ancak, bazı mükemmel ve çok yeniden kullanılabilir HashMap uygulamaları sizin temelinizi oluşturabileceğiniz bazı dışardadır. On ya da iki yıldır Java'da: http://docs.oracle.com/javase/7/docs/api/java/util/HashMap.html

HashMap dahil tüm Java koleksiyonları, yineleyici fikrinden kurtulmak için Java 8 için yeniden yazılır ve bunun yerine dahili olarak yineleyebilmesi için koleksiyona bir işlev geçirmenizi gerektirir. Bu, eşzamanlı kod yazmak için büyük bir kazançtır - koleksiyon, müşterinin zorunda kalmaması için eşzamanlılığı dahili olarak yönetebilir. Bununla birlikte, çoğu programcı işlev işaretlerini geçmek için kullanılmaz. Bu, HashMap'in nereye gittiğini açıklayan iyi bir iş çıkarır: http://cr.openjdk.java.net/~briangoetz/lambda/collections-overview.html

Scala sözdizimi, böyle bir şeyi C'ye çevirmeyi zorlaştırabilir, ancak burada Java 8 koleksiyonlarının olacağı şekilde çalışan bir koleksiyonun örneği - varsayılan olarak değiştirilemez, çeşitli dönüşüm yöntemleri sağlar, işlev işaretçileri ve diğerlerini kullanır kısmi uygulama gibi harika şeyler: http://www.scala-lang.org/api/current/index.html#scala.collection.Map

HashMaps ve diğer daha gelişmiş veri yapılarıyla çok fazla şey yapıyorsanız, bunları destekleyen bir dile bakmak isteyebilirsiniz.

Genel olarak kod çoğaltma fikrine geri dönersek, kesip yapıştırarak gerçekten hızlı kodlayabilirsiniz, ancak korkunç bir kod üretir. Tek seferlik raporlar gibi atma kodunda kabul edilebilir diyebilirim. Ayrıca, başka hiçbir şeye bağlı olmayan bir ekran oluşturan UI kodu gibi, aşağı doğru bir kod olmadığında da bazen sorun yok, ancak gerçekten en iyi ihtimalle gri bir alan.

Kendinizi kod çoğaltırken bulduğunuzda bir işlev yapmanız gerektiğini söylemek için fazla basitleştirme olduğunu sanmıyorum. Kodun bir kopyasında diğeri değil, düzelttiğiniz tek başına hatalar bir işlev yapmak için yeterli bir nedendir.


1
- "Kod çoğaltırken her bulduğunuzda bir işlev yapmanız gerektiğini söylemek için fazla basitleştirme olduğunu düşünmüyorum" - Çok fazla basitleştirme olan C'de. Her veri türü için push ve pop işlevlerini çoğaltmadan tamsayılar, çiftler ve dizeler için C'de Stack yapmayı deneyin.
mike30

@mike Geçersiz işaretçiler kullanmak mümkün değil mi?
sakisk

@mike - iyi bir nokta. Ancak bunun karşı tarafı, Stack'i her bir veri türü için Stack'ı tekrar uygulamanız gerekmeyecek şekilde uygulayabilmenizdir. Yani amaç numberOfImplementations = 1 değil, daha çok 5 gibi. intUygulamayı chars için çalıştırabilirsiniz ... C'yi kullandığımdan beri hatırlayamadığım çok uzun zaman oldu.
GlenPeterson

1
@faif. Evet, ancak geçersiz işaretçiler kullanma ihtiyacı C için önemli bir noktaya işaret etmektedir. Kod çoğaltılması, C kodlaması yaparken olası seçenekler listesinde biraz daha yüksektir
mike30 21

@ mike30 Şununla ilgili herhangi bir kanıt / referans var mı?
sakisk

0

Genel veri yapıları ve genel olarak C yazmak söz konusu olduğunda kod çoğaltma gerekli bir kötülük olup olmadığını merak ediyorum?

C, kesinlikle benim için, C ve C ++ arasında sıçrayan biri olarak. Kesinlikle günlük olarak C ++ 'dan daha önemsiz şeyleri çoğaltırım, ama kasıtlı olarak ve mutlaka "kötü" olarak görmüyorum çünkü en azından bazı pratik faydalar var - bence her şeyi düşünmek bir hata kesinlikle "iyi" ya da "kötü" gibi - hemen hemen her şey bir değiş tokuş meselesidir. Bu ödünleşimleri net bir şekilde anlamak, gözden geçirmedeki pişmanlık verici kararlardan kaçınmamak ve sadece "iyi" veya "kötü" olarak etiketlemek genellikle bu incelikleri göz ardı eder.

Diğerlerinin işaret ettiği gibi problem C'ye özgü olmasa da, jenerikler için makrolardan veya boş işaretçilerden daha şık bir şey olmaması, önemsiz olmayan OOP'nin garipliği ve C standart kütüphanesinde kap yoktur. C ++ 'da, kendi bağlantılı listelerini uygulayan bir kişi, öğrenci olmadıkça neden standart kütüphaneyi kullanmadığını isteyen öfkeli bir grup olabilir. C programcısının en azından günlük olarak bu tür şeyleri yapabilmesi beklendiğinden, C'de, uykunuzda zarif bir bağlantılı liste uygulaması yapamıyorsanız, öfkeli bir çete davet edersiniz. O' Linus Torvalds'ın, dili anlayan ve "iyi bir tadı" olan bir programcıyı değerlendirmek için bir ölçüt olarak SLL arama ve kaldırma uygulamasını SLL arama ve kaldırma uygulamasını kullandığı bazı tuhaf saplantılar nedeniyle değil. Çünkü C programcılarının bu mantığı kariyerinde binlerce kez uygulaması gerekebilir. C için bu durumda, yeni bir aşçının becerilerini değerlendirerek, en azından her zaman yapmaları gereken temel şeylere hakim olup olmadıklarını görmek için biraz yumurta hazırlamalarını sağlayan bir şef gibi.

Örneğin, muhtemelen bu temel "dizine alınmış ücretsiz liste" veri yapısını, bu tahsis stratejisini kullanan her site için yerel olarak C üzerinde bir düzine kez uyguladım (bir kerede bir düğümü tahsis etmekten ve belleği yarıya indirmek için neredeyse tüm bağlantılı yapılarım 64 bit bağlantıların maliyeti):

resim açıklamasını buraya girin

Ancak C'de, yalnızca, bu reallocdiziyi kullanan yeni bir veri yapısı uygulanırken, serbest bırakılabilir bir diziye çok az miktarda kod alır ve ücretsiz bir listeye indeksli bir yaklaşım kullanarak bir miktar bellek biriktirir.

Şimdi aynı şey C ++ 'da uygulandı ve orada sadece bir kez bir sınıf şablonu olarak uyguladık. Ancak C ++ tarafında yüzlerce kod satırı ve yüzlerce kod satırını da kapsayan bazı dış bağımlılıklar ile çok, çok daha karmaşık bir uygulama. Ve çok daha karmaşık olmasının ana nedeni, Tolası herhangi bir veri türü olabileceği fikrine karşı kodlamak zorunda olduğumdur . Herhangi bir zamanda (standart kütüphane kaplarında olduğu gibi açıkça yapmak zorunda olduğum yok ederken), hafızayı ayırmak için uygun hizalamayı düşünmek zorunda kaldım.T (Neyse ki bu C ++ 11'den itibaren daha kolay olsa da), önemsizce inşa edilemez / yıkılabilir olabilir (yeni ve manuel dtor çağrıları gerektirir), her şeyin ihtiyaç duymayacağı, ancak bazı şeylerin ihtiyaç duyacağı yöntemler eklemeliyim, ve hem değişebilir hem de salt okunur (sabit) yineleyiciler, vb. yineleyiciler eklemeliyim.

Büyüyen Diziler Roket Bilimi Değil

C ++ 'da insanlar bunu std::vectorölüme göre optimize edilmiş bir roket bilim insanının çalışması gibi ses çıkarırlar, ancak sadece reallocbir geri itme ile dizi kapasitesini arttırmak için kullanılan belirli bir veri türüne kodlanmış dinamik bir C dizisinden daha iyi performans göstermezler. düzine kod satırı. Aradaki fark, sadece büyüyebilen rastgele erişimli bir dizinin standarda tam uyumlu olmasını sağlamak için çok karmaşık bir uygulama gerektiriyor, eklenmemiş öğeler üzerinde ctors çağırmaktan kaçınmak, istisna açısından güvenli, hem const hem de const olmayan rasgele erişim yineleyicileri sağlamak, kullanım türü bazı entegre tipler için doldurma cetvellerini aralık ctorlarından ayırma özellikleriT, potansiyel olarak POD'lara tip özelliklerini vb. kullanarak farklı davranın. Bu noktada, gerçekten de, sadece büyümeye hazır bir dinamik dizi yapmak için çok karmaşık bir uygulamaya ihtiyacınız var, ancak sadece akla gelebilecek her olası kullanım durumunu ele almaya çalıştığı için. Artı tarafta, hem POD'ları hem de önemsiz olmayan UDT'leri gerçekten depolamanız gerekiyorsa, herhangi bir uyumlu veri yapısında çalışan genel yineleyici tabanlı algoritmalar için kullanmanız gerekiyorsa, tüm bu ekstra çabadan çok fazla kilometre alabilirsiniz, istisna işleme ve RAII'den faydalanın, en azından bazen std::allocatorkendi özel ayırıcı vb. ile geçersiz kılın vb. Ne kadar fayda sağladığınızı düşündüğünüzde kesinlikle standart kütüphanede işe yarar.std::vector onu kullanan insanların tüm dünyasında vardı, ama bu tüm dünya ihtiyaçlarını hedeflemek için tasarlanmış standart kütüphanede uygulanan bir şey için.

Çok Özel Kullanım Durumlarını İşleme Daha Basit Uygulamalar

Sadece "endeksli ücretsiz listem" ile çok özel kullanım durumlarını ele almanın bir sonucu olarak, bu ücretsiz listenin C tarafında bir düzine kez uygulanmasına ve sonuç olarak bazı önemsiz kodların çoğaltılmasına rağmen, muhtemelen daha az kod yazdım C ++ 'da yalnızca bir kez uygulamak zorunda olduğumdan bir düzine kez daha uygulamak için C ve toplamda bu C ++ uygulamasını korumak zorunda olduğumdan daha fazla zaman harcamak zorunda kaldım. C tarafının bu kadar basit olmasının ana nedenlerinden biri, bu tekniği her kullandığımda genellikle C'deki POD'larla çalışıyorum ve genellikle daha fazla işleve ihtiyacım yokinsert veerasebunu yerel olarak uyguladığım belirli sitelerde. Temelde sadece C ++ sürümünün sağladığı işlevselliğin en telafi alt kümesini uygulayabilirim, çünkü çok özel bir kullanım için uyguladığımda ne yaptığım ve tasarım gerekmediği hakkında çok daha fazla varsayım yapmakta özgürüm durum.

Şimdi C ++ sürümü çok daha hoş ve tipte güvenli, ancak yine de istisna-güvenli ve çift yönlü yineleyici uyumlu, örneğin bir genel, yeniden kullanılabilir uygulamanın gelmesi gibi maliyetler uygulamak ve uygulamak için büyük bir PITA oldu bu durumda tasarruf ettiğinden daha fazla zaman. Ve genelleştirilmiş bir şekilde uygulama maliyetinin büyük bir kısmı sadece önceden değil, tekrar tekrar her gün tekrarlanan ödeme süreleri gibi şeyler şeklinde boşa harcanmaktadır.

C ++ 'a Saldırı Değil!

Ama bu C ++ 'a bir saldırı değil çünkü C ++' ı seviyorum, ancak veri yapıları söz konusu olduğunda, C ++ 'ı özellikle uygulamak için çok fazla zaman harcamak istediğim önemsiz olmayan veri yapıları için tercih etmeye geldim. çok genel bir yol, tüm olası türlere karşı istisna-güvenli hale getirmeT hale getirme, standart uyumlu ve yinelenebilir hale getirme, vb., bu tür ön maliyetlerin gerçekten bir ton kilometre şeklinde ödediği.

Ancak bu aynı zamanda çok farklı bir tasarım zihniyetini de teşvik eder. C ++ çarpışma tespiti için bir Octree yapmak istiyorsanız, ben n. Dereceye genellemek için bir eğilim var. Sadece indeksli üçgen kafesleri depolamak istemiyorum. Neden parmak uçlarımda süper güçlü bir kod oluşturma mekanizması olduğunda çalışabileceği tek bir veri türüyle sınırlandırmalıyım ki bu, çalışma zamanında tüm soyutlama cezalarını ortadan kaldırıyor? Prosedür kürelerini, küpleri, vokselleri, NURB yüzeylerini, nokta bulutlarını vb. Vb. Saklamasını ve her şey için iyi yapmaya çalışmasını istiyorum, çünkü parmaklarınızın ucunda şablonlar olduğunda bu şekilde tasarlamak istemek cazip geliyor. Çarpışma tespiti ile sınırlamak bile istemeyebilirim - ışın izleme, toplama vb. C ++ başlangıçta "sorta kolay" görünmesini sağlar bir veri yapısını nnci derecede genelleştirmek. Ve ben böyle bir uzamsal indeksleri C ++ ile tasarlıyordum. Onları tüm dünyanın açlık ihtiyaçlarını karşılayacak şekilde tasarlamaya çalıştım ve karşılığında aldığım şey, akla gelebilecek tüm olası kullanım durumlarına karşı dengelemek için son derece karmaşık bir kod içeren bir "tüm işlemlerin krikosu" idi.

Ne kadar tuhaf olsa da, yıllar içinde C'de uyguladığım uzamsal dizinlerden daha fazla yeniden kullanıldım ve C ++ hatası yok, ama sadece dilin beni cezbedeceği şeyde benim. C'de bir oktree gibi bir şeyi kodladığımda, sadece puanlarla çalışmasını sağlama ve bununla mutlu olma eğilimim var, çünkü dil, onu nci dereceye kadar genelleştirmeyi denemeyi çok zorlaştırıyor. Ancak bu eğilimler nedeniyle, yıllar boyunca aslında daha verimli ve güvenilir ve bazı görevler için gerçekten uygun olan şeyleri tasarlama eğilimindeydim, çünkü bunlar nci dereceye kadar genel olmakla uğraşmıyorlar. Tüm esnafların krikosu yerine özel bir kategoride as haline gelirler. Yine bu C ++ hatası değil, sadece C yerine aksine kullandığım insan eğilimleri geliyor.

Her neyse, her iki dili de seviyorum ama farklı eğilimler var. CI'de yeterince genelleme eğilimi yoktur. C ++ 'da çok fazla genelleme eğilimim var. İkisini de kullanmak kendimi dengelememe yardımcı oldu.

Genel uygulamalar bir norm mu yoksa her kullanım durumu için farklı uygulamalar mı yazıyorsunuz?

Bir diziden veya kendi kendini yeniden tahsis eden bir diziden düğümleri kullanarak tek başına bağlı 32 bit dizinli listeler gibi önemsiz şeyler için ( std::vector C ++ 'da ) veya diyelim ki, noktaları saklayan ve daha fazlasını yapmayı amaçlayan bir oktree için, t Herhangi bir veri türünü saklama noktasına genelleme yapmak zahmetine girer. Bunları belirli bir veri türünü saklamak için uyguluyorum (yine de soyut olabilir ve bazı durumlarda işlev işaretçileri kullanabilir, ancak en azından statik polimorfizmle ördek tiplemesinden daha spesifik olabilir).

Ve sağlanan durumlarda biraz fazlalık ile mükemmel bir şekilde mutluyum bunu iyice test etmem , . Birim testi yapmazsam, artıklık hataları çok daha rahatsız edici hissetmeye başlar, çünkü hataları çoğaltabilecek gereksiz kodlarınız olabilir, örneğin Sonra yazdığınız kod türünün tasarım değişikliklerine ihtiyaç duyması muhtemel olmasa bile, değişikliklere ihtiyaç duyabilir çünkü bozuldu. Neden olarak yazdığım C kodu için daha kapsamlı birim testleri yazma eğilimindeyim.

Önemsiz şeyler için, genellikle C ++ için ulaştığımda, ancak C'de uygulayacak olsaydım void*, sadece işaretçiler kullanmayı düşünürdüm , belki de her öğe için ne kadar bellek copy/destroyayıracağını ve muhtemelen işlev işaretleyicilerini bilmek için bir tür boyutunu kabul ederdim önemsiz bir şekilde oluşturulamaz / imha edilemezse verileri derin bir şekilde kopyalayıp yok etmek. Çoğu zaman rahatsız etmiyorum ve en karmaşık veri yapılarını ve algoritmaları oluşturmak için çok fazla C kullanmıyorum.

Belirli bir veri türüyle yeterince sık bir veri yapısı kullanırsanız, yalnızca bit ve void*baytlarla ve işlev işaretleyicileriyle çalışan ve örneğin tür güvenliğini C sarmalayıcısı aracılığıyla yeniden düzenlemek için, tür güvenli bir sürümü de sarabilirsiniz .

Örneğin karma harita için genel bir uygulama yazmaya çalışabilirdim, ama her zaman sonuçların dağınık olduğunu düşünüyorum. Ayrıca, sadece bu özel kullanım durumu için özel bir uygulama yazabilir, kodu açık ve okunması ve hata ayıklaması kolay tutabilirim. İkincisi elbette bazı kodların çoğaltılmasına yol açacaktır.

Karma tablolar bir tür iffy'dir, çünkü tablonun otomatik olarak kendi başına büyümesini veya tablo boyutunu Açık adresleme veya ayrı zincirleme vb. kullanın tam olarak bu ihtiyaçlara göre ayarlandığında fazlalıklı olmayın. En azından yerel olarak bir şey uygularsam kendime verdiğim mazeret bu. Değilse, yukarıda açıklanan yöntemi void*ve işlev işaretleyicilerini bir şeyleri kopyalamak / yok etmek ve genelleştirmek için kullanabilirsiniz.

Genellikle çok genelleştirilmiş veri yapısını yenmek için çok çaba veya çok kod almaz eğer sizin alternatif tam kullanım alanına son derece dar geçerlidir. Örnek olarak, mallocher bir düğüm için (birçok düğüm için bir grup belleği bir araya toplamanın aksine) kullanma performansını bir kez ve kesinlikle çok, çok kesin bir kullanım durumu için tekrar ziyaret etmek zorunda kalmamanız kesinlikle önemsizdir. daha yeni uygulamalar mallocortaya çıktığında bile . Onu yenmek ve hayatınızın büyük bir bölümünü genelliğine uymak istiyorsanız onu güncellemek ve güncel tutmak için ayırmanız daha az karmaşık bir kodlama yapmak bir ömür sürebilir.

Başka bir örnek olarak, Pixar veya Dreamworks tarafından sunulan VFX çözümlerinden 10 kat daha hızlı veya daha fazla çözüm uygulamayı genellikle çok kolay buldum. Uykumda yapabilirim. Ama bunun nedeni, uygulamalarımın çok daha üstün olduğu için değil. Çoğu insan için düpedüz aşağıdırlar. Sadece çok, çok özel kullanım durumlarım için üstündürler. Sürümlerim Pixar veya Dreamwork'lerden çok daha az genel olarak uygulanabilir. Çözümleri aptalca basit çözümlerime kıyasla kesinlikle mükemmel olduğu için gülünç derecede haksız bir karşılaştırma, ama bu bir nokta. Karşılaştırmanın adil olması gerekmez. İhtiyacınız olan tek şey çok spesifik birkaç şeyse, ihtiyacınız olmayan şeylerin sonsuz bir listesini işlemek için bir veri yapısı yapmanız gerekmez.

Homojen Bitler ve Baytlar

C'de sömürülecek bir şey, bu tür bir doğal güvenlik eksikliğine sahip olduğundan, bitlerin ve baytların özelliklerine dayanarak işleri homojen bir şekilde saklama fikri. Bellek ayırıcı ve veri yapısı arasında bir bulanıklık daha var.

Ancak bir grup değişken boyutlu şeyi, hatta bir polimorfik gibi ve sadece değişken boyutlu olabilecek şeyleri depolamak verimli bir şekilde yapmak zordur. Değişken boyutta olabileceği varsayımıyla gidemez ve bunları basit bir rasgele erişimli kapta bitişik olarak saklayamazsınız, çünkü bir öğeden diğerine alma adımı farklı olabilir. Sonuç olarak, hem köpekleri hem de kedileri içeren bir listeyi saklamak için 3 ayrı veri yapısı / ayırıcı örneği (biri köpekler, biri kediler için, diğeri polimorfik baz işaretçileri veya akıllı işaretçiler listesi için veya daha kötüsü kullanmanız gerekebilir. , her bir köpeği ve kediyi genel amaçlı bir ayırıcıya ayırın ve onları hafızanın her tarafına dağıtın), bu da pahalı hale gelir ve çoğaltılmış önbellek özlemlerinden payını alır.DogCat

Bu nedenle, C'de kullanılacak bir strateji, azaltılmış tip zenginliği ve güvenliği olmasına rağmen, bit ve bayt düzeyinde genellemektir. Bir fonksiyon işaretçi tablosuna aynı sayıda bit ve bayt, aynı alanlara, aynı işaretçiye sahip olduğunu Dogsve Catsgerekli olduğunu varsayabilirsiniz . Ancak karşılığında daha az veri yapısını kodlayabilirsiniz, ancak en önemlisi, tüm bunları verimli ve bitişik olarak saklayabilirsiniz. Bu durumda köpeklere ve kedilere analog sendikalar gibi davranıyorsunuz (ya da aslında bir birlik kullanabilirsiniz).

Ve bu, güvenliği yazmak için büyük bir maliyet getirmektedir. C'de her şeyden daha fazla özlediğim bir şey varsa, bu tür güvenlik. Yapıların sadece ne kadar bellek ayırdığını ve her veri alanının nasıl hizalandığını gösteren montaj seviyesine yaklaşıyor. Ama bu aslında C'yi kullanmamın bir numaralı sebebi Gerçekten bellek düzenlerini kontrol etmeye çalışıyorsanız ve her şeyin nerede tahsis edildiği ve her şeyin birbirine göre depolandığı yerlerde, genellikle bitler düzeyinde şeyler düşünmeye yardımcı olur ve bayt ve belirli bir sorunu çözmek için ne kadar bit ve bayt gerekir. Orada C tipi sistemin dilsizliği aslında bir handikaptan ziyade faydalı olabilir. Genellikle bununla başa çıkmak için çok daha az veri türü elde edilir,

Hayali / Görünür Çoğaltma

Artık gereksiz bile olmayan şeyler için "çoğaltma" yı gevşek bir anlamda kullanıyorum. İnsanların "tesadüfi / belirgin" çoğaltma gibi terimleri "gerçek çoğaltma" dan ayırdığını gördüm. Gördüğüm gibi, birçok durumda böyle net bir ayrım yok. Ayrımı daha çok "potansiyel teklik" ve "potansiyel çoğaltma" gibi buluyorum ve her iki şekilde de gidebilir. Genellikle tasarımlarınızın ve uygulamalarınızın nasıl gelişmesini istediğinize ve belirli bir kullanım durumu için ne kadar mükemmel bir şekilde uyarlanacağına bağlıdır. Ama sık sık ne kod çoğaltma gibi görünebilir daha sonra birkaç iyileştirme yineleme sonra artık gereksiz olduğu ortaya çıktı.

Kullanarak realloc, basit bir büyütülebilir dizi uygulaması almak , analog eşdeğer std::vector<int>. Başlangıçta, örneğin std::vector<int>C ++ ile kullanmak gereksiz olabilir . Ancak, ölçüm yoluyla, yığın tahsisine gerek kalmadan on altı 32 bit tamsayının eklenmesine izin vermek için önceden 64 bayt önceden konumlandırmanın yararlı olabileceğini görebilirsiniz. Şimdi artık gereksiz değil, en azından ile std::vector<int>. Ve sonra diyebilirsiniz ki, "Ama bunu sadece yeni bir genelleme yapabilirdim SmallVector<int, 16>ve siz de yapabilirsiniz. Ama diyelim ki yararlı buluyorsunuz, çünkü bunlar çok küçük, kısa ömürlü diziler için yığın dağılımındaki dizi kapasitesini dört katına çıkarmak için 1,5 oranında (kabacavectordizi kullanımı her zaman iki güç olarak kabul edilir. Şimdi konteyneriniz gerçekten farklı ve muhtemelen bunun gibi bir konteyner yok. Ve belki de bu tür davranışları, önceden konumlandırma ağırlığını özelleştirmek, yeniden konumlandırma davranışını özelleştirmek vb. kodu.

Ayrıca, 256 bit hizalanmış ve yastıklı bellek ayıran, AVX 256 talimatları için özel olarak POD depolayan, yaygın küçük giriş boyutları için yığın tahsisini önlemek için 128 bayt önceden konumlandıran, kapasiteyi iki katına çıkaran bir veri yapısına ihtiyacınız olan bir noktaya bile ulaşabilirsiniz. ve dizi boyutunu aşan ancak dizi kapasitesini aşmayan sondaki öğelerin güvenli bir şekilde üzerine yazılmasına olanak tanır. Bu noktada, az miktarda C kodunu kopyalamaktan kaçınmak için hala bir çözüm bulmaya çalışıyorsanız, programlama tanrılarının ruhunuza merhameti olabilir.

Bu nedenle, başlangıçta gereksiz görünmeye başlayan şeyin büyümeye başladığı, aynı zamanda, belirli bir kullanım örneğini, tamamen benzersiz ve gereksiz olmayan bir şeye daha iyi ve daha iyi ve daha iyi uyacak şekilde uyarladığınız zamanlar da vardır. Ancak bu sadece onları belirli bir kullanım durumuna mükemmel bir şekilde uyarlayabileceğiniz şeyler için. Bazen sadece amacımız için genelleştirilmiş "iyi" bir şeye ihtiyacımız var ve orada çok genelleştirilmiş veri yapılarından en iyi şekilde faydalanıyorum. Ancak, belirli bir kullanım durumu için mükemmel bir şekilde yapılan istisnai şeyler için, "genel amaç" ve "amacım için mükemmel yapılmış" fikri çok geçimsiz olmaya başlar.

POD'lar ve İlkeller

Şimdi C'de, POD'ları ve özellikle de ilkelleri mümkün olduğunda veri yapılarına depolamak için sıklıkla bahaneler buluyorum. Bu bir anti-desen gibi görünebilir ama aslında yanlışlıkla C ++ daha sık yapmak için şeyler türleri üzerinde kod sürdürülebilirliğini artırmak konusunda yararlı buldum.

Basit bir örnek staj yapmak kısa dizeler (tipik olarak arama anahtarları için kullanılan dizelerde olduğu gibi - çok kısa olma eğilimindedir). Neden boyutları çalışma zamanında değişen tüm bu değişken uzunluklu dizelerle uğraşmak, önemsiz olmayan inşaat ve yıkımı ima ediyor (çünkü ayırmayı ve ücretsiz olarak yığınlandırmamız gerekebilir)? Bu şeyleri, yalnızca dize stajyerliği için tasarlanmış bir iş parçacığı için güvenli trie veya karma tablo gibi merkezi bir veri yapısında depolamaya ve daha sonra eski int32_tya da düz bir dizeye bakın :

struct IternedString 
{
    int32_t index;
};

... karma tablolarımızda, kırmızı-siyah ağaçlarımızda, listelerimizi atladığımızda vb. sözlüksel sıralamaya ihtiyacımız yoksa? Şimdi 32-bit tamsayılarla çalışmak için kodladığımız diğer tüm veri yapılarımız şimdi sadece 32-bit olan bu interned dize anahtarlarını saklayabilir ints. Ve en azından kullanım durumlarımda buldum (ışın izleme, ağ işleme, görüntü işleme, parçacık sistemleri, komut dosyası dillerine bağlanma, düşük seviyeli çok iş parçacıklı GUI kiti uygulamaları vb. düşük seviyeli şeyler, ancak bir işletim sistemi kadar düşük seviyeli değil), kodun tesadüfen daha verimli ve daha basit hale gelmesi, sadece endeksleri bu tür şeylere depolamasıdır. Bu yüzden sık sık çalışıyorum, diyelim ki zamanın% 75'i, sadece int32_tvefloat32 önemsiz olmayan veri yapılarımda veya sadece aynı boyutta (neredeyse her zaman 32 bit) şeyleri depolamak.

Doğal olarak durumunuz için geçerliyse, ilk etapta çok azıyla çalışacağınız için farklı veri türleri için bir dizi veri yapısı uygulamasından kaçınabilirsiniz.

Test ve Güvenilirlik

Sunduğum son şey ve herkes için olmayabilir, bu veri yapıları için testler yazmayı tercih etmek. Bir şeyde onları gerçekten iyi yap. Ultra güvenilir olduklarından emin olun.

Bazı küçük kod çoğaltmaları bu durumlarda çok daha affedilebilir hale gelir, çünkü kod çoğaltma yalnızca çoğaltılan kodda basamaklı değişiklikler yapmanız gerektiğinde bir bakım yüküdür. Bu gereksiz kodun değişmesinin başlıca nedenlerinden birini, son derece güvenilir olduğundan ve yapması gerekmeyen şeyler için gerçekten uygun olduğundan emin olarak ortadan kaldırırsınız.

Estetik anlayışım yıllar içinde değişti. Bir kitaplık nokta ürün uygulamak veya başka bir zaten uygulanmış bazı önemsiz SLL mantığı görüyorum çünkü artık tedirgin değil. Sadece işler kötü test edildiğinde ve güvenilmez olduğunda sinirlenirim ve bunu çok daha üretken bir zihniyet buldum. Gerçekten çoğaltılmış kod ile çoğaltılmış hata kod kodları ile ele ve birçok hataya eğilimli basamaklı bir değişiklik haline bir merkezi yere önemsiz bir değişiklik olması gereken kopyala ve yapıştır kodlama en kötü durumları gördük. Ancak o zamanların çoğu, kötü testlerin, kodun ilk etapta yaptığı işte güvenilir ve iyi hale gelememesinin sonucuydu. Daha önce buggy eski kod tabanlarında çalışırken, zihnim tüm kod çoğaltma biçimlerini hataları çoğaltma ve basamaklı değişiklikler gerektirme olasılığı çok yüksek olarak ilişkilendirdi. Yine de, bir şeyi son derece iyi ve güvenilir bir şekilde yapan bir minyatür kütüphane, burada ve orada gereksiz görünen bir kod olsa bile gelecekte değişmek için çok az neden bulacaktır. Çoğaltma kalitesizlik ve test eksikliğinden daha fazla beni rahatsız ettiğinde önceliklerim geri döndü. Bu ikinci şeyler en öncelikli konu olmalıdır.

Minimalizm İçin Kod Çoğaltma?

Bu kafamda ortaya çıkan komik bir düşünce, ancak kabaca aynı şeyi yapan bir C ve C ++ kütüphanesi ile karşılaşabileceğimiz bir durumu düşünün: her ikisi de kabaca aynı işlevselliğe, aynı miktarda hata işlemeye sahip, biri önemli değil Ve en önemlisi, her ikisi de yetkin bir şekilde uygulanmış, iyi test edilmiş ve güvenilirdir. Ne yazık ki burada yan yana karşılaştırmaya hiç yakın bir şey bulamadığım için burada varsayımsal olarak konuşmalıyım. Ancak bu yan yana karşılaştırmaya şimdiye kadar bulduğum en yakın şey, C kütüphanesinin genellikle C ++ eşdeğerinden (bazen kod boyutunun 1 / 10'u) çok daha küçük olmasına neden oldu.

Ve bunun nedeninin, bir problemi, tam bir kullanım durumu yerine en geniş kullanım durumlarını ele alan genel bir şekilde çözmek için yüzlerce ila binlerce satırlık kod gerektirebileceğine inanıyorum. düzine. Gerekliliğe rağmen ve C standart kütüphanesinin standart veri yapıları sağlama konusunda uçsuz bucaksız olmasına rağmen, aynı problemleri çözmek için genellikle insan elinde daha az kod üretilmesine neden olur ve bence bu öncelikle bu iki dil arasındaki insan eğilimlerindeki farklılıklara Biri çok özel bir kullanım durumuna karşı şeyleri çözmeyi teşvik eder, diğeri en geniş kullanım alanına karşı daha soyut ve genel çözümleri teşvik etme eğilimindedir, ancak bunların sonucu yoktur. '

Geçen gün github üzerinde birinin raytracer bakıyordum ve C ++ 'da uygulandı ve bir oyuncak raytracer için çok kod gerekli. Ve koda bakarak o kadar zaman harcamadım ama orada bir raytracer'ın gerekenden çok daha fazla yol alan genel amaçlı yapıların bir yükü vardı. Ve bu kodlama tarzını tanıyorum çünkü C ++ 'ı bir tür süper aşağıdan yukarıya tarzda aynı şekilde kullanıyordum, ilk önce hemen ötesine geçen çok genel amaçlı veri yapılarından oluşan tam bir kütüphane oluşturmaya odaklandım. eldeki sorun ve daha sonra asıl sorunla mücadele ikinci. Ancak bu genel yapılar, burada ve orada bir miktar fazlalığı ortadan kaldırabilir ve yeni bağlamlarda yeniden kullanımın tadını çıkarabilirken, karşılığında, gereksiz kod / işlevsellikten oluşan bir tekne yükü ile biraz fazlalık alışverişi yaparak projeyi büyük ölçüde şişiriyorlar ve ikincisinin bakımı öncekinden daha kolay değildir. Aksine, denge tasarım kararlarını mümkün olan en geniş ihtiyaç yelpazesine karşı sıkıştırmak zorunda olan genel bir şeyin tasarımını sürdürmek zor olduğundan, çoğu zaman sürdürülmesi daha zor olur.

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.