C ++ STL neden şablonlara bu kadar dayanıyor? (* arayüzlerde * değil)


211

Yani, zorlayıcı adının yanı sıra (Standart Şablon Kütüphanesi) ...

C ++ başlangıçta OOP kavramlarını C'ye sunmayı amaçladı. Yani: belirli bir varlığın sınıfını ve sınıf hiyerarşisine dayanarak ne yapabileceğini ve yapamayacağını söyleyebilirdiniz. Bazı yetenek kompozisyonlarının çoklu kalıtım problematiği ve C ++ 'ın arayüz kavramını biraz sakar bir şekilde (java vb. İle karşılaştırıldığında) desteklemesi nedeniyle bu şekilde tanımlanması daha zordur, ancak orada (ve olabilir gelişmiş).

Ve sonra STL ile birlikte şablonlar devreye girdi. STL, klasik OOP konseptlerini alıp şablonlar kullanarak onları akıttı.

Şablonların, kendilerinin türlerinin şablonun çalışmasıyla ilgisiz olduğu türleri genelleştirmek için kullanıldığı durumlar arasında bir ayrım olmalıdır (örneğin kaplar). Bir Having vector<int>çok mantıklı.

Bununla birlikte, diğer birçok durumda (yineleyiciler ve algoritmalar), şablonlanmış türlerin, kavramın gerçek ayrıntılarının tamamen şablonun uygulanmasıyla tanımlandığı bir "kavramı" (Giriş Yineleyici, İleri Yineleyici, vb.) İzlemesi gerekir. işlev / sınıf, şablonla kullanılan türün sınıfına göre değil, bu biraz OOP'nin anti-kullanımıdır.

Örneğin, işlevi söyleyebilirsiniz:

void MyFunc(ForwardIterator<...> *I);

Güncelleme: Orijinal soruda belirsiz olduğu için, ForwardIterator, herhangi bir ForwardIterator türüne izin vermek için kendisinin ayarlanması için uygundur. Aksine ForwardIterator'ı bir kavram olarak kullanmaktır.

bir İleri İteratörün yalnızca uygulamaya veya belgelere bakmanız gereken tanımına bakarak bekler:

template <typename Type> void MyFunc(Type *I);

Şablon kullanımı lehine yapabileceğim iki iddia: derlenmiş kod, vtables kullanmak yerine her kullanılan tip için şablonu özel olarak derleyerek daha verimli hale getirilebilir. Ve şablonların yerel türlerle kullanılabilmesi.

Bununla birlikte, klasik OOP'yi STL için ayartma lehine terk etmenin daha derin bir nedenini arıyorum? (Şu ana kadar okuduğunuzu varsayarsak: P)


4
Stackoverflow.com/questions/31693/… adresine bakabilirsiniz . Kabul edilen cevap, şablonların size jenerikler üzerinden sunduklarının mükemmel bir açıklamasıdır.
James McMahon

6
@Jonas: Bu hiç mantıklı değil. Önbellek üzerindeki kısıtlama saat döngülerine mal olur, bu yüzden önemlidir. Günün sonunda, performansı tanımlayan önbellek değil saat çevrimleridir. Bellek ve önbellek yalnızca harcanan saat döngülerini etkilediği sürece önemlidir. Dahası, deney kolayca yapılabilir. Diyelim ki, std :: for_Each işlevini bir işlev bağımsız değişkeni ile eşdeğer OOP / vtable yaklaşımıyla karşılaştırın. Performanstaki fark şaşırtıcı . Bu yüzden şablon sürümü kullanılır.
jalf

7
ve yedek kodun icache'yi doldurmasının bir nedeni yoktur. Programımda <char> vektörünü ve <int> vektörünü somutlaştırırsam, <int> vektörünü işlerken neden vektör <char> kodu icache'ye yüklenmeli? Aslında, <int> vektörü için kod kesilir, çünkü döküm, vtable ve dolaylı kod dahil etmek zorunda değildir.
jalf

3
Alex Stepanov , miras ve eşitliğin neden birlikte iyi oynadıklarını açıklıyor .
fredoverflow

6
@BerndJendrissek: Uhm, yakın ama kendin değilsin. Evet, gerçekten kullanılıyorsa , bellek bant genişliği ve önbellek kullanımı açısından daha fazla kod maliyeti . Ancak a vector<int>ve vector<char>aynı anda kullanılmasını beklemek için özel bir neden yoktur . Bunlar tabii, belki, ancak kullanabileceği herhangi aynı anda iki parça kod. Bunun şablonlar, C ++ veya STL ile ilgisi yoktur. Anında kodun yüklenmesini veya yürütülmesini vector<int>gerektiren hiçbir şey yoktur vector<char>.
jalf

Yanıtlar:


607

Kısa cevap "C ++ hareket ettiği için" dir. Evet, 70'lerin sonlarında Stroustrup, OOP yeteneklerine sahip yükseltilmiş bir C oluşturmayı amaçladı, ancak bu çok uzun zaman önceydi. Dil 1998'de standartlaştırıldığında, artık bir OOP dili değildi. Çok paradigma diliydi. Kesinlikle OOP kodu için bazı destekleri vardı, ama aynı zamanda bir turing-tamamlanmış şablon dilinin üzerine yerleştirilmişti, derleme zamanı meta programlamasına izin verdi ve insanlar genel programlamayı keşfetti. Aniden, OOP o kadar önemli görünmüyordu. Şablonlar ve genel programlama yoluyla mevcut teknikleri kullanarak daha basit, daha özlü ve daha verimli kod yazabildiğimizde değil .

OOP kutsal kâse değil. Bu sevimli bir fikirdi ve icat edildiğinde 70'lerde prosedürel diller üzerinde oldukça bir gelişme oldu. Ama dürüst olmak gerekirse, bu sadece kırılmış değil. Birçok durumda bu beceriksiz ve ayrıntılıdır ve yeniden kullanılabilir kod veya modülerliği teşvik etmez.

Bu nedenle C ++ topluluğu bugün genel programlamaya çok daha fazla ilgi duyuyor ve herkes nihayet fonksiyonel programlamanın da oldukça zeki olduğunu anlamaya başlıyor. OOP tek başına güzel bir manzara değil.

Varsayımsal "OOP-ified" STL'nin bağımlılık grafiğini çizmeyi deneyin. Birbirleri hakkında kaç sınıfın bilmesi gerekir? Çok fazla bağımlılık olurdu . İçeri vectorgirmeden iteratorve hatta iostreamiçeri çekmeden yalnızca başlığı ekleyebilir misiniz ? STL bunu kolaylaştırır. Bir vektör, tanımladığı yineleyici türünü bilir ve hepsi bu. STL algoritmaları hiçbir şey bilmez . Yineleyicileri parametre olarak kabul etseler bile bir yineleyici başlığı eklemelerine bile gerek yoktur. Hangisi daha modüler?

Java'nın tanımladığı gibi STL, OOP kurallarına uymayabilir, ancak OOP hedeflerine ulaşamaz mı? Yeniden kullanılabilirlik, düşük bağlantı, modülerlik ve kapsülleme elde edemiyor mu?

Ve bu hedeflere OOP ified versiyonundan daha iyi ulaşamıyor mu?

STL'nin neden dile kabul edildiğine gelince, STL'ye yol açan birkaç şey oldu.

İlk olarak, şablonlar C ++ 'a eklendi. Genetiklerin .NET'e eklenmesi ile aynı nedenden ötürü eklendi. Tip güvenliğini atmadan "T tipi kaplar" gibi şeyler yazabilmek iyi bir fikir gibi görünüyordu. Tabii ki, yerleştikleri uygulama oldukça daha karmaşık ve güçlüydü.

Sonra insanlar ekledikleri şablon mekanizmasının beklenenden daha güçlü olduğunu keşfettiler. Birisi daha genel bir kütüphane yazmak için şablonları kullanmayı denemeye başladı. Biri fonksiyonel programlamadan ilham aldı ve diğeri C ++ 'nın tüm yeni yeteneklerini kullandı.

O kadar garip ve farklı, ama sonuçta fark görünüyordu çünkü buna alışık büyümek için oldukça zaman aldı C ++ dil komitesi sunduk onlar başka türlü dahil etmek olurdu Geleneksel OOP eşdeğeri daha çok işe yaradı . Böylece birkaç ayar yaptılar ve standart kütüphaneye uyarladılar.

Bu ideolojik bir seçim değildi, "OOP olmak istiyor muyuz değil miyiz?" Kütüphaneyi değerlendirdiler ve çok iyi çalıştığını gördüler.

Her durumda, STL'yi tercih etmek için bahsettiğiniz nedenlerin her ikisi de kesinlikle önemlidir.

C ++ standart kütüphane sahip verimli olması için. Örneğin, eşdeğer bir haddelenmiş C kodundan daha az verimli olursa, insanlar bunu kullanmaz. Bu verimliliği düşürür, hata olasılığını artırır ve genel olarak kötü bir fikir olur.

Ve STL ilkel tiplerle çalışmak zorundadır , çünkü ilkel tipler C'de sahip olduğunuz tek şeydir ve her iki dilin de büyük bir parçasıdır. Eğer STL yerel dizilerle çalışmazsa, işe yaramazdı .

Sorunuzun OOP'un "en iyi" olduğuna dair güçlü bir varsayımı var. Nedenini merak ediyorum. Neden "klasik OOP'u terk ettiklerini" soruyorsunuz. Neden onunla sıkışmış olmaları gerektiğini merak ediyorum. Ne gibi avantajları olurdu?


22
İyi bir yazı, ama bir ayrıntıyı vurgulamak istiyorum. STL, C ++ 'ın bir "ürünü" değildir. Aslında, bir kavram olarak STL, C ++ 'dan önce vardı ve C ++, jenerik programlama için (neredeyse) yeterli güce sahip etkili bir dil oldu, bu nedenle STL C ++' da yazıldı.
Igor Krivokon

17
Yorumlar ortaya çıkmaya devam ettiğinden, evet, STL adının belirsiz olduğunu biliyorum. Ama "STL üzerinde modellenen C ++ standart kütüphanesinin parçası" için daha iyi bir isim düşünemiyorum. Standart kütüphanenin bu bölümü için fiili isim olan o kesinlikle yanlış olsa bile sadece "STL". :) İnsanlar STL'yi tüm standart kütüphanenin (IOStreams ve C stdlib başlıkları dahil) adı olarak kullanmadığı sürece mutluyum. :)
jalf

5
@einpoklum Ve soyut bir temel sınıftan tam olarak ne kazanırsınız? Örnek std::setolarak alalım. Soyut bir temel sınıftan miras almaz. Bu, kullanımınızı nasıl sınırlar std::set? std::setSoyut bir temel sınıftan miras almadığı için yapamayacağınız bir şey var mı ?
fredoverflow 19:14

22
@einpoklum, Alan Kay'ın OOP terimini icat ettiğinde OOP dili olarak tasarladığı Smalltalk diline bir göz atın. Arabirimleri yoktu. OOP, arayüzler veya soyut temel sınıflarla ilgili değildir. Bunu "aklındaki vadeli OOP mucidi C ++ daha OOP ne benzemiyor Java, demek mısın ayrıca vadeli OOP mucidi aklındakini benzemiyor"? Söylemek istediğiniz şey "C ++ benim zevkime uygun Java gibi değil" dir. Bu adil, ama OOP ile ilgisi yok.
jalf

8
@MasonWheeler, bu cevap bir grup açık saçmalıksa, kelimenin tam anlamıyla dünya çapında yüzlerce geliştiricinin bunun için + 3 oy verdiğini + 3 oy verdiğini görmezsiniz
panda-34

88

Sorduğunuz / şikayet ettiğiniz hakkında en doğrudan cevap şudur: C ++ 'ın bir OOP dili olduğu varsayımı yanlış bir varsayımdır.

C ++ çok paradigma bir dildir. OOP prensipleri kullanılarak programlanabilir, prosedürel olarak programlanabilir, jenerik olarak programlanabilir (şablonlar) ve C ++ 11 (eski adıyla C ++ 0x) bazı şeyler işlevsel olarak programlanabilir.

C ++ tasarımcıları bunu bir avantaj olarak görüyorlar, bu yüzden genel programlama sorunu daha iyi çözdüğünde ve daha genel olarak , geriye doğru bir adım olacağı zaman C ++ 'ın sınırlandırılmasının tamamen OOP dili gibi davranacağını iddia ederler .


4
"ve C ++ 0x ile bazı şeyler işlevsel olarak programlanabilir" - bu özellikler olmadan işlevsel olarak programlanabilir, sadece daha ayrıntılı olarak.
Jonas Kölker

3
@Tyler Gerçekten de C ++ 'ı saf OOP ile sınırlarsanız, Objective-C ile kalırsınız.
Justicle

@TylerMcHenry: Sadece sorduktan sonra bu , sadece seninle aynı cevabı çıkardı ettik bulun! Sadece bir nokta. Keşke Standart Kütüphane'nin Nesneye Dayalı kod yazmak için kullanılamayacağı gerçeğini eklemek isterdim.
einpoklum

74

Anladığım kadarıyla Stroustrup başlangıçta bir "OOP tarzı" konteyner tasarımını tercih etti ve aslında bunu yapmanın başka bir yolunu görmedi. Alexander Stepanov STL'den sorumlu olanı ve hedefleri "nesneyi yöneltir" içermiyordu :

Temel nokta budur: algoritmalar cebirsel yapılar üzerinde tanımlanır. Düzenli aksiyomlara karmaşıklık gereklilikleri ekleyerek yapı kavramını genişletmeniz gerektiğini fark etmem birkaç yıl daha aldı. ... yineleyici teorilerinin Bilgisayar Bilimi için halka teorileri kadar önemli olduğuna inanıyorum. Bir algoritmaya her baktığımda, üzerinde tanımlandığı bir yapı bulmaya çalışırdım. Yani yapmak istediğim algoritmaları genel olarak tanımlamaktı. Bunu yapmaktan hoşlanıyorum. Bir ayını jenerik temsilini bulmaya çalışan iyi bilinen bir algoritma üzerinde çalışarak geçirebilirim. ...

STL, en azından benim için, programlamanın mümkün olan tek yolunu temsil ediyor. Gerçekten de, sunulduğu ve hala birçok ders kitabında sunulduğu gibi C ++ programlamasından oldukça farklıdır. Ama, görüyorsunuz, C ++ ile program yapmaya çalışmıyordum, yazılımla başa çıkmanın doğru yolunu bulmaya çalışıyordum. ...

Birçok yanlış başlangıç ​​yaptım. Örneğin, bu mekanizmanın neden temelde kusurlu olduğunu ve kullanılmaması gerektiğini anlamadan önce, kalıtım ve sanallar için bazı kullanımlar bulmaya çalıştım. Kimsenin tüm ara adımları göremediğinden çok mutluyum - çoğu aptalca.

(Miras ve sanalların - diğer bir deyişle nesne yönelimli tasarımın "temelde kusurlu olduğunu ve röportajın geri kalanında kullanılmaması gerektiğini" açıklar).

Stepanov kütüphanesini Stroustrup, Stroustrup ve diğerlerine sunduktan sonra ISO C ++ standardına girmek için herculean çabalarından geçti (aynı röportaj):

Bjarne Stroustrup'un desteği çok önemliydi. Bjarne gerçekten standartta STL istiyordu ve eğer Bjarne bir şey istiyorsa, anlıyor. ... hatta beni STL'de başka hiç kimseye yapmayacağım değişiklikler yapmaya zorladı ... o tanıdığım en tek fikirli kişi. İşleri halleder. STL'nin neyle ilgili olduğunu anlaması biraz zaman aldı, ancak yaptığında, onu zorlamaya hazırdı. Ayrıca, on yıldan fazla bir süre boyunca pullanma ve yutturmacaya karşı birden fazla programlama yolunun geçerli olduğu ve esneklik, verimlilik, aşırı yükleme ve tip güvenliği kombinasyonunun devam ettiği görüşüne dayanarak STL'ye katkıda bulundu. STL'yi mümkün kılan şablonlar. Bjarne'nin benim kuşağımın en önde gelen dil tasarımcısı olduğunu açıkça belirtmek isterim.


2
İlginç röportaj. Bir süre önce okudum eminim, ama kesinlikle tekrar geçmeye değerdi. :)
jalf

3
Şimdiye kadar okuduğum programlama hakkında en ilginç röportajlardan biri. Beni daha fazla ayrıntı için susuzluk
bırakmasına

Java ("Java'da bir tür iki argüman alan ve aynı türde bir dönüş değeri olan genel bir max () yazamazsınız)" gibi diller hakkında yaptığı şikayetlerin çoğu yalnızca çok eski sürümlerle ilgilidir jenerikler eklenmeden önce. Başından beri bile, jeneriklerin nihayetinde ekleneceği biliniyordu, ancak (bir kez uygulanabilir bir sözdizimi / anlambilimi anlaşıldığında), bu yüzden eleştirileri büyük ölçüde temelsizdi. Evet, statik olarak yazılan bir dilde tip güvenliğini korumak için bazı biçimlerdeki jeneriklere ihtiyaç vardır, ancak hayır, OO'yu değersiz hale getirmez.
Bazı Guy

1
@SomeGuy Java hakkında şikayette bulunmuyorlar. SmallTalk'ın veya " Java'nın " "standart" OO programlamasından bahsediyor . Röportaj 90'lı yılların sonlarında (2000 yılında AT&T'de çalışmak üzere bıraktığı SGI'da çalıştığını belirtiyor). Jenerikler sürüm 1.5'te yalnızca Java'ya 2004'te eklenmiştir ve bunlar "standart" OO modelinden bir sapmadır.
Melpomene

24

Cevap, STL'nin yazarı Stepanov ile yapılan bu röportajda bulunur :

Evet. STL nesne yönelimli değildir. Nesne yöneliminin Yapay Zeka kadar neredeyse bir aldatmaca olduğunu düşünüyorum. Henüz bu OO insanlarından gelen ilginç bir kod parçası görmedim.


Güzel mücevher; Hangi yıl geldiğini biliyor musun?
Kos

2
@Kos göre web.archive.org/web/20000607205939/http://www.stlport.org/... bağlantılı sayfanın ilk versiyonunu altındaki sayfa kendisi Telif diyor 7 Haziran 2001 dan 2001- 2008.
alfC

@Kos Stepanov ilk cevapta SGI'da çalışıyor. Mayıs 2000'de SGI'dan ayrıldı, bu yüzden röportaj bundan daha büyük.
Melpomene

18

Veri Yapısı ve Algoritmalar Kütüphanesi için saf bir OOP tasarımı neden daha iyi olur?! OOP her şeyin çözümü değildir.

IMHO, STL şimdiye kadar gördüğüm en zarif kütüphanedir :)

sorunuz için,

çalışma zamanı polimorfizmine ihtiyacınız yoktur, STL'nin kütüphaneyi statik polimorfizm kullanarak uygulaması aslında bir avantajdır, bu da verimlilik anlamına gelir. Genel bir Sıralama veya Mesafe veya TÜM kapsayıcılar için geçerli olan algoritmayı yazmaya çalışın! Java'daki Sıralama özelliğiniz n düzeylerinde dinamik olan işlevleri yürütür!

Saf OOP dillerinin kötü varsayımlarını gizlemek için Boks ve Kutudan Çıkarma gibi aptalca bir şeye ihtiyacınız var.

STL ve genel olarak şablonlarda gördüğüm tek sorun korkunç hata mesajları. Hangi C ++ 0X kavramlar kullanılarak çözülecektir.

Java'daki Koleksiyonlarla STL'yi karşılaştırmak, Tac Mahal'i evimle karşılaştırmak gibidir :)


12
Ne, Tac Mahal küçük ve zarif ve eviniz bir dağın büyüklüğü ve tam bir karmaşa mı? ;)
jalf

Kavramlar artık c ++ 0x'in bir parçası değil. Bazı hata mesajları static_assertbelki de önceden kullanılabilir .
KitsuneYMG

GCC 4.6, şablon hata mesajlarını geliştirdi ve 4.7 + 'nın onunla daha da iyi olduğuna inanıyorum.
David Stone

Bir Kavram aslında OP'nin istediği "arayüz" dür. Tek fark, bir Konseptten "miras" ın örtük olması (bir sınıfın tüm doğru üye işlevlerine sahip olması durumunda, otomatik olarak Konsept'in bir alt türüdür) (bir Java sınıfının açıkça bir arabirim uyguladığını beyan etmesi gerekir) . Bununla birlikte, hem örtük hem de açık alt tipleme geçerli OO'dur ve bazı OO dilleri, tıpkı Kavramlar gibi çalışan örtük kalıtıma sahiptir. Burada söylenen şey temelde "OO berbat: şablonlar kullanın. Ancak şablonların sorunları var, bu yüzden Kavramlar (OO olan) kullanın."
Bazı Guy

11

şablonlu türlerin, kavramın asıl ayrıntılarının türün sınıfı tarafından değil, tamamen şablon işlevinin / sınıfının uygulanmasıyla tanımlandığı bir "kavram" (Girdi Yineleyici, İleri Yönlendirici vb.) izlemesi gerekir. OOP'un biraz anti-kullanımı olan şablonla kullanılır.

Bence kavramların şablonlarla kullanım amacını yanlış anlıyorsunuz. Örneğin İleri Iterator, çok iyi tanımlanmış bir kavramdır. Bir sınıfın İleri Yineleyici olması için geçerli olması gereken ifadeleri ve hesaplama karmaşıklığı da dahil olmak üzere semantiklerini bulmak için standarda veya http://www.sgi.com/tech/stl/ForwardIterator.html adresine bakın. (hepsini görmek için Giriş, Çıkış ve Önemsiz Yineleyici bağlantılarını izlemeniz gerekir).

Bu belge son derece iyi bir arayüz ve "kavramın gerçek detayları" burada tanımlanıyor. Forward Iterator uygulamaları tarafından tanımlanmazlar ve Forward Iterator kullanan algoritmalar tarafından da tanımlanmazlar.

STL ve Java arasındaki arabirimlerin nasıl işlendiği arasındaki farklar üç katlıdır:

1) STL, nesneyi kullanarak geçerli ifadeleri tanımlar, Java ise nesne üzerinde çağrılabilir yöntemleri tanımlar. Elbette geçerli bir ifade bir yöntem (üye işlevi) çağrısı olabilir, ancak olması gerekmez.

2) Java arayüzleri çalışma zamanı nesneleridir, oysa STL kavramları RTTI ile bile çalışma zamanında görünmez.

3) Bir STL konsepti için gerekli geçerli ifadeleri geçerli hale getiremezseniz, tür ile bazı şablonlar başlattığınızda belirtilmemiş bir derleme hatası alırsınız. Java arabiriminin gerekli bir yöntemini uygulayamazsanız, bunu söyleyerek belirli bir derleme hatası alırsınız.

Bu üçüncü bölüm, bir tür (derleme zamanı) "ördek yazması" ndan hoşlanıyorsanız: arayüzler örtük olabilir. Java'da arayüzler biraz açıktır: bir sınıf "yalnızca" Yinelemeyi uyguladığını söylüyorsa yinelenebilir. Derleyici, yöntemlerinin imzalarının hepsinin mevcut ve doğru olup olmadığını kontrol edebilir, ancak anlambilim hala örtüktür (yani ya belgelenir ya da edilmez, ancak yalnızca daha fazla kod (birim testleri) uygulamanın doğru olup olmadığını söyleyebilir).

C ++ 'da, Python'da olduğu gibi, hem semantik hem de sözdizimi örtüktür, ancak C ++' da (ve güçlü yazılan önişlemciyi alırsanız Python'da) derleyiciden biraz yardım alırsınız. Bir programcı, uygulayıcı sınıf tarafından Java benzeri açık arabirim bildirimi gerektiriyorsa, standart yaklaşım tür özelliklerini kullanmaktır (ve çoklu kalıtım bunun çok ayrıntılı olmasını engelleyebilir). Java ile karşılaştırıldığında eksik olan şey, yazımla başlatabildiğim ve yalnızca gerekli tüm ifadeler yazım için geçerli olduğunda derlenecek tek bir şablon. Bu bana gerekli tüm bitleri "kullanmadan önce" uygulayıp uygulamadığımı söylerdi. Bu bir kolaylıktır, ancak OOP'nin çekirdeği değildir (ve hala anlambilimi test etmez,

STL, zevkinize göre yeterince OO olabilir veya olmayabilir, ancak kesinlikle arayüzü temiz bir şekilde uygulamadan ayırır. Java'nın arabirimler üzerinde yansıma yapma yeteneğinden yoksundur ve arabirim gereksinimlerinin ihlali farklı şekilde bildirilir.

İşlevi anlayabilirsiniz ... bir İleri Yineleyici yalnızca tanımına bakarak, uygulamaya ya da belgelere bakmanız gereken ...

Şahsen, uygun şekilde kullanıldığında örtük türlerin bir güç olduğunu düşünüyorum. Algoritma, şablon parametreleriyle ne yaptığını söyler ve uygulayıcı bu şeylerin çalışmasını sağlar: "arayüzlerin" ne yapması gerektiği tam olarak ortak paydadır. Dahası, STL ile, örneğin, std::copybir başlık dosyasında ileri bildirimini bulmaya dayalı olarak kullanmanız pek olası değildir . Programcılar , bir işlevin yalnızca işlev imzasına değil, belgelerine dayanarak ne aldığını da hesaplamalıdır. Bu C ++, Python veya Java için geçerlidir. Herhangi bir dilde yazarak neler yapılabileceğine dair sınırlamalar vardır ve yazmayı yapmadığı bir şeyi yapmak için kullanmaya çalışmak (anlambilimi kontrol etmek) bir hata olur.

Bununla birlikte, STL algoritmaları genellikle şablon parametrelerini hangi kavramın gerekli olduğunu açık bir şekilde adlandırır. Ancak bu, ileri bildirimleri daha bilgilendirici hale getirmek için değil, belgelerin ilk satırında yararlı ek bilgiler sağlamaktır. Bilmeniz gereken, parametre türlerinde kapsüllenebileceğinden daha fazla şey var, bu yüzden belgeleri okumalısınız. (Örneğin, bir giriş aralığı ve bir çıkış yineleyicisi alan algoritmalarda, çıkış yineleyicisinin, giriş aralığının büyüklüğüne ve belki de içindeki değerlere bağlı olarak belirli sayıda çıkış için yeterli "alana" ihtiyacı vardır. )

İşte açıkça beyan edilen arayüzlerde Bjarne: http://www.artima.com/cppsource/cpp0xP.html

Jenerikte bir argüman, jenerik tanımında belirtilen bir arabirimden (arabirime C ++ eşdeğeri soyut sınıftır) türetilmiş bir sınıftan olmalıdır. Bu, tüm genel argüman türlerinin bir hiyerarşiye sığması gerektiği anlamına gelir. Bu tasarımlar üzerinde gereksiz kısıtlamalar getirir, geliştiriciler açısından mantıksız öngörü gerektirir. Örneğin, bir genel yazarsanız ve bir sınıf tanımlarsam, belirttiğiniz arabirimi bilmiyor ve sınıfımı ondan türetmiş olmadıkça, insanlar sınıfımı jenerikinize argüman olarak kullanamazlar. Bu katı.

Diğer yöne baktığımızda, ördek yazarak arayüzün var olduğunu bilmeden bir arayüz uygulayabilirsiniz. Ya da birisi kasıtlı olarak bir arayüz yazabilir, böylece sınıfınız onu uygular ve dokümanlarınıza zaten yapmadığınız bir şey istemediklerini görmek için danışır. Bu esnek.


Açıkça bildirilen arabirimlerde iki kelime: sınıfları yazın. (Bu zaten Stepanov'un "konsept" ile ne anlama geldiğini gösteriyor.)
pyon

"Bir STL konsepti için gerekli geçerli ifadeleri geçerli hale getiremezseniz, türüyle bazı şablonlar başlattığınızda belirtilmemiş bir derleme hatası alırsınız." -- bu yanlış. stdBir kavramla uyuşmayan kütüphaneye bir şey iletmek genellikle "biçimsizdir, teşhis gerektirmez".
Yakk - Adam Nevraumont

Doğru, "geçerli" terimi ile hızlı ve gevşek oynuyordum. Derleyici gerekli ifadelerden birini derleyemezse, o zaman bir şey rapor edecek demek istedim.
Steve Jessop

8

"OOP bana sadece mesajlaşma, yerel tutma ve devlet sürecinin korunması ve gizlenmesi ve her şeyin aşırı geç bağlanması anlamına gelir. Smalltalk ve LISP'de yapılabilir. Muhtemelen bunun mümkün olduğu başka sistemler de vardır, ancak Onların farkında değilim. " - Alan Kay, Smalltalk'in yaratıcısı.

C ++, Java ve diğer birçok dil, klasik OOP'tan oldukça uzaktır. Bununla birlikte, ideolojileri savunmak son derece üretken değildir. C ++ hiçbir anlamda saf değildir, bu nedenle o zaman pragmatik bir anlam ifade eden işlevsellik uygular.


7

STL, en sık kullanılan algoritmayı kapsayan tutarlı bir davranış ve performans hedefi olan geniş bir kütüphane sağlamak amacıyla başladı . Şablon, bu uygulamayı ve hedefi mümkün kılmak için önemli bir faktör olarak geldi.

Başka bir referans sağlamak için:

Al Stevens, DDJ'nin Mart 1995'te Alex Stepanov ile röportajlar:

Stepanov, çalışma deneyimini ve sonunda STL'ye dönüşen geniş bir algoritma kütüphanesine yönelik seçimini açıkladı.

Bize jenerik programlamaya uzun vadeli ilginiz hakkında bir şeyler söyleyin

..... Sonra C ++ kütüphanelerinde C ++ grubunda çalışan Bell Laboratories'de bir iş teklifi aldım. Bana C ++ ile yapıp yapamayacağımı sordular. Tabii ki, C ++ bilmiyordum ve tabii ki yapabileceğimi söyledim. Ancak C ++ ile yapamadım, çünkü 1987'de C ++ 'ın bu programlama tarzını etkinleştirmek için gerekli şablonlar yoktu. Kalıtım, jenerikliği elde etmenin tek mekanizmasıydı ve yeterli değildi.

Şimdi bile C ++ kalıtım jenerik programlama için çok fazla kullanılmıyor. Nedenini tartışalım. Birçok kişi, veri yapılarını ve kap sınıflarını uygulamak için kalıtım kullanmaya çalıştı. Şimdi bildiğimiz gibi, başarılı girişimler varsa çok az şey vardı. C ++ kalıtım ve onunla ilişkili programlama stili önemli ölçüde sınırlıdır. Kullanarak eşitlik kadar önemsiz bir şey içeren bir tasarım uygulamak imkansızdır. Hiyerarşinizin kökünde bir temel X sınıfı ile başlar ve bu sınıfta X türünde bir argüman alan bir sanal eşitlik operatörü tanımlarsanız, X sınıfından X sınıfını türetirsiniz. Eşitliğin arayüzü nedir? Y'yi X ile karşılaştıran bir eşitliğe sahiptir. Hayvanları örnek olarak kullanmak (OO insanları hayvanları sever), memeliyi tanımlar ve memeliden zürafa türetir. Sonra bir üye işlev montaj ilişkisi tanımlayın, burada hayvan hayvan ile çiftleşir ve bir hayvan döndürür. Daha sonra zürafayı hayvandan türetirsiniz ve elbette, zürafanın hayvanla birleştiği ve bir hayvanı döndürdüğü bir işlev arkadaşı vardır. Kesinlikle istediğiniz şey değil. Çiftleşme C ++ programcıları için çok önemli olmasa da eşitliktir. Bir çeşit eşitliğin kullanılmadığı tek bir algoritma bilmiyorum.


5

İle ilgili temel sorun

void MyFunc(ForwardIterator *I);

yineleyicinin döndürdüğü şeyin türünü nasıl güvenli bir şekilde elde edersiniz? Şablonlarla, bu sizin için derleme zamanında yapılır.


1
Ben de: 1. Genel kod yazdığım için elde etmeye çalışmayın. Ya da, 2. C ++ bu günlerde sunduğu yansıma mekanizmasını kullanarak alın.
einpoklum

2

Bir an için standart kütüphaneyi temel olarak koleksiyonlar ve algoritmalar veritabanı olarak düşünelim.

Eğer veritabanlarının tarihini incelediyseniz, hiç şüphesiz başlangıçta veritabanlarının çoğunlukla "hiyerarşik" olduğunu biliyorsunuzdur. Hiyerarşik veritabanları klasik OOP'ye çok yakındı - özellikle, Smalltalk tarafından kullanılan gibi tek miras çeşitliliği.

Zamanla, hiyerarşik veritabanlarının neredeyse her şeyi modellemek için kullanılabileceği anlaşıldı, ancak bazı durumlarda tek miras modeli oldukça sınırlayıcıydı. Ahşap bir kapınız varsa, ona bir kapı olarak ya da bir hammadde parçası olarak (çelik, ahşap, vb.) Bakmak kullanışlı oldu.

Böylece ağ modeli veritabanlarını icat ettiler. Ağ modeli veritabanları çoklu kalıtıma çok yakındır. Java sınırlı bir formu desteklerken (yalnızca tek bir sınıftan miras alabilirsiniz, ancak istediğiniz kadar çok arabirim uygulayabilirsiniz) C ++ birden çok kalıtımı tamamen destekler.

Hem hiyerarşik model hem de ağ modeli veritabanları çoğunlukla genel amaçlı kullanımdan kaybolmuştur (ancak birkaçı oldukça spesifik nişlerde kalmaktadır). Çoğu amaç için, bunların yerini ilişkisel veritabanları almıştır.

İlişkisel veritabanlarının devralma nedeninin çoğu çok yönlülüktür. İlişkisel model işlevsel olarak ağ modelinin bir üst kümesidir (bu da hiyerarşik modelin bir üst kümesidir).

C ++ büyük ölçüde aynı yolu izlemiştir. Tek miras ve hiyerarşik model ile çoklu miras ve ağ modeli arasındaki yazışma oldukça açıktır. C ++ şablonları ve hiyerarşik model arasındaki yazışma daha az belirgin olabilir, ancak yine de oldukça yakındır.

Bunun resmi bir kanıtı görmedim, ancak şablonların yeteneklerinin çoklu miras tarafından sağlananların bir süper seti olduğuna inanıyorum (ki bu açıkça tek bir mirasın üst kümesi). Zor olan bir nokta, şablonların çoğunlukla statik olarak bağlı olmasıdır - yani, tüm bağlanma çalışma zamanında değil derleme zamanında gerçekleşir. Bu nedenle, kalıtımın kalıtım yeteneklerinin bir üst kümesini sağladığı resmi bir kanıt, biraz zor ve karmaşık olabilir (hatta imkansız olabilir).

Her durumda, C ++ 'ın kapları için miras kullanmamasının gerçek nedeninin çoğunun bu olduğunu düşünüyorum - miras, şablonlar tarafından sağlanan özelliklerin yalnızca bir alt kümesini sağladığı için bunun gerçek bir nedeni yoktur. Şablonlar temelde bazı durumlarda bir zorunluluk olduğundan, neredeyse her yerde kullanılabilirler.


0

ForwardIterator * ile karşılaştırmaları nasıl yapıyorsunuz? Yani, sahip olduğunuz öğenin aradığınız şey olup olmadığını nasıl kontrol edersiniz?

Çoğu zaman, böyle bir şey kullanırdım:

void MyFunc(ForwardIterator<MyType>& i)

yani ben MyType'ın işaret olduğunu biliyorum ve bunları karşılaştırmak biliyorum. Bir şablon gibi görünse de, gerçekten değildir ("şablon" anahtar kelimesi yok).


sadece türün <,> ve = operatörlerini kullanabilir ve bunların ne olduğunu bilmiyorsunuz (bu ne demek istediğinizi olmayabilir)
lhahne

Bağlama bağlı olarak, bunlar bir anlam ifade etmeyebilir veya iyi çalışabilir. Kullanıcının tahmin ettiği gibi, yaptığımız ve yapmadığımız MyType hakkında daha fazla bilgi sahibi olmadan bunu söylemek zor.
Tanktalus

0

Bu sorunun birçok harika cevabı var. Şablonların açık bir tasarımı desteklediği de belirtilmelidir. Nesneye yönelik programlama dillerinin mevcut durumu ile, bu tür sorunlarla uğraşırken ziyaretçi desenini kullanmak gerekir ve gerçek OOP çoklu dinamik bağlamayı desteklemelidir. Bkz . C ++ için Açık Çoklu Yöntemler, P. Pirkelbauer, et.al. çok aralıklı okuma için.

Şablonların bir başka ilginç noktası, çalışma zamanı polimorfizmi için de kullanılabilmeleridir. Örneğin

template<class Value,class T>
Value euler_fwd(size_t N,double t_0,double t_end,Value y_0,const T& func)
    {
    auto dt=(t_end-t_0)/N;
    for(size_t k=0;k<N;++k)
        {y_0+=func(t_0 + k*dt,y_0)*dt;}
    return y_0;
    }

Bu işlevin Valuebir çeşit vektör ise de işe yarayacağına dikkat edin ( karışıklıktan kaçınmak için std :: vector değilstd::dynamic_array )

Eğer funcküçük, bu işlev inlining çok şey kazanacaktır. Örnek kullanım

auto result=euler_fwd(10000,0.0,1.0,1.0,[](double x,double y)
    {return y;});

Bu durumda, kesin cevabı bilmelisiniz (2.718 ...), ancak temel çözüm olmadan basit bir ODE oluşturmak kolaydır (İpucu: y'de bir polinom kullanın).

Şimdi, içinde büyük bir ifadeniz var funcve ODE çözücüyü birçok yerde kullanıyorsunuz, böylece çalıştırılabilir öğeniz her yerde şablon örneklemeleriyle kirleniyor. Ne yapalım? Dikkat edilmesi gereken ilk şey, normal bir işlev işaretçisinin çalıştığıdır. Sonra bir arayüz ve açık bir örnekleme yazmak için köri eklemek istiyorsunuz

class OdeFunction
    {
    public:
        virtual double operator()(double t,double y) const=0;
    };

template
double euler_fwd(size_t N,double t_0,double t_end,double y_0,const OdeFunction& func);

Ancak yukarıdaki örnekleme, yalnızca doublearabirimi şablon olarak yazmamaya çalışır:

template<class Value=double>
class OdeFunction
    {
    public:
        virtual Value operator()(double t,const Value& y) const=0;
    };

ve bazı ortak değer türleri için uzmanlaşmak:

template double euler_fwd(size_t N,double t_0,double t_end,double y_0,const OdeFunction<double>& func);

template vec4_t<double> euler_fwd(size_t N,double t_0,double t_end,vec4_t<double> y_0,const OdeFunction< vec4_t<double> >& func); // (Native AVX vector with four components)

template vec8_t<float> euler_fwd(size_t N,double t_0,double t_end,vec8_t<float> y_0,const OdeFunction< vec8_t<float> >& func); // (Native AVX vector with 8 components)

template Vector<double> euler_fwd(size_t N,double t_0,double t_end,Vector<double> y_0,const OdeFunction< Vector<double> >& func); // (A N-dimensional real vector, *not* `std::vector`, see above)

Eğer fonksiyon önce bir arayüz etrafında tasarlanmış olsaydı, o zaman bu ABC'den miras almak zorunda kalırdınız. Artık bu seçeneğin yanı sıra işlev işaretçisi, lambda veya başka bir işlev nesnesi var. Buradaki anahtar, sahip olmamız gerektiğidir operator()()ve dönüş türünde bazı aritmetik işleçleri kullanabilmeliyiz. Böylece, C ++ operatörün aşırı yüklenmesi durumunda şablon makineleri bu durumda kırılacaktır.


-1

Arayüzü arayüzden ayırma ve uygulamaları değiştirebilme kavramı Nesne Odaklı Programlamaya özgü değildir. Bunun Microsoft COM gibi Bileşen Tabanlı Geliştirme'de ortaya çıkmış bir fikir olduğuna inanıyorum. ( Bileşene Dayalı Gelişim Nedir? Hakkındaki cevabımı görün. ) C ++ 'da büyüyüp öğrenen insanlar kalıtım ve çok biçimlilikten vazgeçtiler. 90'lı yıllara kadar, "bir uygulama" değil "bir" arayüz "için program" ve "sınıf mirası" üzerinde "nesne kompozisyonu" lehine "demeye başladı. (her ikisi de bu arada GoF'den alıntılanmıştır).

Sonra Java, yerleşik çöp toplayıcı ve interfaceanahtar sözcükle birlikte geldi ve aniden arayüz ve uygulamayı ayırmak pratik hale geldi. Bilmeden önce fikir OO'nun bir parçası oldu. C ++, şablonlar ve STL bunların hepsinden önce gelir.


Arayüzlerin sadece OO olmadığını kabul etti. Ancak tip sistemindeki yetenek polimorfizmi (60'larda Simula'daydı). Modula-2 ve Ada'da modül arayüzleri mevcuttu, ancak bunların tip sisteminde işletildiğini düşünüyorum.
andygavin
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.