Maksud saya, selain dari nama wajibnya (Perpustakaan Template Standar) ...
C ++ awalnya dimaksudkan untuk menyajikan konsep OOP ke dalam C. Yaitu: Anda dapat mengetahui apa yang bisa dan tidak bisa dilakukan entitas tertentu (terlepas dari bagaimana melakukannya) berdasarkan kelas dan hierarki kelasnya. Beberapa komposisi kemampuan lebih sulit untuk dijelaskan dengan cara ini karena problematika multiple inheritance, dan fakta bahwa C ++ mendukung konsep antarmuka dengan cara yang agak canggung (dibandingkan dengan java, dll), tetapi itu ada (dan bisa jadi ditingkatkan).
Dan kemudian templat ikut bermain, bersama dengan STL. STL tampaknya mengambil konsep OOP klasik dan membuangnya, menggunakan template sebagai gantinya.
Seharusnya ada perbedaan antara kasus ketika templat digunakan untuk menggeneralisasi jenis-jenis di mana jenis-jenis themeelves tidak relevan untuk pengoperasian templat (wadah, misalnya). Memiliki perasaan yang vector<int>
masuk akal.
Namun, dalam banyak kasus lain (iterator dan algoritma), tipe templated seharusnya mengikuti "konsep" (Input Iterator, Forward Iterator, dll ...) di mana rincian sebenarnya dari konsep tersebut ditentukan sepenuhnya oleh implementasi template fungsi / kelas, dan bukan oleh kelas dari jenis yang digunakan dengan templat, yang merupakan anti-penggunaan OOP.
Misalnya, Anda dapat memberi tahu fungsinya:
void MyFunc(ForwardIterator<...> *I);
Pembaruan: Karena tidak jelas dalam pertanyaan awal, ForwardIterator boleh digunakan untuk mengizinkan jenis ForwardIterator apa pun. Yang sebaliknya adalah memiliki ForwardIterator sebagai sebuah konsep.
mengharapkan Iterator Teruskan hanya dengan melihat definisi, di mana Anda perlu melihat implementasi atau dokumentasi untuk:
template <typename Type> void MyFunc(Type *I);
Dua klaim yang saya dapat mendukung penggunaan templat: kode yang dikompilasi dapat dibuat lebih efisien, dengan menyesuaikan templat untuk setiap jenis yang digunakan, daripada menggunakan vtables. Dan fakta bahwa templat dapat digunakan dengan tipe asli.
Namun, saya mencari alasan yang lebih mendalam mengapa meninggalkan OOP klasik demi templating untuk STL? (Dengan asumsi Anda membaca sejauh itu: P)
vector<int>
danvector<char>
digunakan pada saat yang bersamaan. Mereka mungkin, yakin, tetapi Anda mungkin menggunakan salah dua buah kode pada waktu yang sama. Itu tidak ada hubungannya dengan template, C ++ atau STL. Tidak ada dalam Instansiasivector<int>
yang memerlukanvector<char>
kode untuk dimuat atau dieksekusi.Jawaban:
Jawaban singkatnya adalah "karena C ++ telah pindah". Ya, kembali di akhir 70-an, Stroustrup bermaksud untuk membuat C ditingkatkan dengan kemampuan OOP, tapi itu sudah lama sekali. Pada saat bahasa itu distandarisasi pada tahun 1998, itu bukan lagi bahasa OOP. Itu adalah bahasa multi-paradigma. Itu tentu saja memiliki beberapa dukungan untuk kode OOP, tetapi juga memiliki bahasa template turing-complete overlay, memungkinkan metaprogramming waktu kompilasi, dan orang-orang telah menemukan pemrograman generik. Tiba-tiba, OOP sepertinya tidak terlalu penting. Tidak ketika kita dapat menulis kode yang lebih sederhana, lebih ringkas dan lebih efisien dengan menggunakan teknik yang tersedia melalui template dan pemrograman generik.
OOP bukan cawan suci. Itu ide yang lucu, dan itu merupakan peningkatan dari bahasa prosedural di tahun 70-an ketika diciptakan. Tapi sejujurnya tidak semua itu kacau. Dalam banyak kasus itu kaku dan bertele-tele dan itu tidak benar-benar mempromosikan kode yang dapat digunakan kembali atau modularitas.
Itulah sebabnya komunitas C ++ saat ini jauh lebih tertarik pada pemrograman generik, dan mengapa semua orang akhirnya mulai menyadari bahwa pemrograman fungsional juga cukup pintar. OOP sendiri bukan pemandangan yang indah.
Cobalah menggambar grafik dependensi dari STL "OOP-ified" hipotetis. Berapa banyak kelas yang harus diketahui tentang satu sama lain? Akan ada banyak ketergantungan. Apakah Anda dapat memasukkan hanya
vector
tajuk, tanpa juga masukiterator
atau bahkaniostream
ditarik? STL membuatnya mudah. Vektor tahu tentang tipe iterator yang didefinisikannya, dan itu saja. Algoritma STL tidak tahu apa - apa . Mereka bahkan tidak perlu menyertakan header iterator, meskipun mereka semua menerima iterator sebagai parameter. Yang mana yang lebih modular?STL mungkin tidak mengikuti aturan OOP karena Java mendefinisikannya, tetapi tidakkah ia mencapai tujuan OOP? Bukankah itu mencapai usabilitas ulang, kopling rendah, modularitas dan enkapsulasi?
Dan bukankah itu mencapai tujuan-tujuan ini lebih baik daripada versi yang di-OOP-kan?
Adapun mengapa STL diadopsi ke dalam bahasa, beberapa hal terjadi yang mengarah ke STL.
Pertama, templat ditambahkan ke C ++. Mereka ditambahkan untuk banyak alasan yang sama bahwa obat generik ditambahkan ke .NET. Tampaknya ide yang bagus untuk dapat menulis hal-hal seperti "wadah tipe T" tanpa membuang keselamatan tipe. Tentu saja, implementasi yang mereka selesaikan jauh lebih kompleks dan kuat.
Kemudian orang-orang menemukan bahwa mekanisme templat yang telah mereka tambahkan bahkan lebih kuat dari yang diharapkan. Dan seseorang mulai bereksperimen dengan menggunakan templat untuk menulis perpustakaan yang lebih umum. Satu terinspirasi oleh pemrograman fungsional, dan yang menggunakan semua kemampuan baru C ++.
Dia mempresentasikannya kepada komite bahasa C ++, yang butuh waktu cukup lama untuk menjadi terbiasa karena itu terlihat sangat aneh dan berbeda, tetapi akhirnya menyadari bahwa itu bekerja lebih baik daripada setara OOP tradisional yang harus mereka sertakan sebaliknya . Jadi mereka membuat beberapa penyesuaian, dan mengadopsinya ke perpustakaan standar.
Itu bukan pilihan ideologis, itu bukan pilihan politik "apakah kita ingin menjadi OOP atau tidak", tetapi pilihan yang sangat pragmatis. Mereka mengevaluasi perpustakaan, dan melihat bahwa itu bekerja dengan sangat baik.
Bagaimanapun, kedua alasan yang Anda sebutkan untuk mendukung STL sangat penting.
C ++ standar perpustakaan memiliki untuk menjadi efisien. Jika kurang efisien daripada, katakanlah, kode C linting tangan yang setara, maka orang tidak akan menggunakannya. Itu akan menurunkan produktivitas, meningkatkan kemungkinan bug, dan secara keseluruhan hanya ide yang buruk.
Dan STL harus bekerja dengan tipe primitif, karena tipe primitif adalah semua yang Anda miliki di C, dan mereka adalah bagian utama dari kedua bahasa. Jika STL tidak bekerja dengan array asli, itu akan sia - sia .
Pertanyaan Anda memiliki asumsi kuat bahwa OOP adalah "terbaik". Saya ingin tahu mengapa. Anda bertanya mengapa mereka "meninggalkan OOP klasik". Saya bertanya-tanya mengapa mereka harus terjebak dengan itu. Keuntungan apa yang akan dimilikinya?
sumber
std::set
sebagai contoh. Itu tidak mewarisi dari kelas dasar abstrak. Bagaimana itu membatasi penggunaan Andastd::set
? Apakah ada sesuatu yang tidak dapat Anda lakukan dengan astd::set
karena tidak mewarisi dari kelas dasar abstrak?Jawaban paling langsung untuk apa yang saya pikir Anda tanyakan / keluhkan adalah: Asumsi bahwa C ++ adalah bahasa OOP adalah asumsi yang salah.
C ++ adalah bahasa multi-paradigma. Ia dapat diprogram menggunakan prinsip-prinsip OOP, dapat diprogram secara prosedural, dapat diprogram secara umum (templat), dan dengan C ++ 11 (sebelumnya dikenal sebagai C ++ 0x) beberapa hal bahkan dapat diprogram secara fungsional.
Perancang C ++ melihat ini sebagai keuntungan, sehingga mereka berpendapat bahwa membatasi C ++ untuk bertindak seperti bahasa OOP murni ketika pemrograman generik memecahkan masalah dengan lebih baik dan, lebih umum lagi , akan menjadi langkah mundur.
sumber
Pemahaman saya adalah bahwa Stroustrup awalnya lebih suka desain wadah "bergaya OOP", dan pada kenyataannya tidak melihat cara lain untuk melakukannya. Alexander Stepanov adalah orang yang bertanggung jawab untuk STL, dan tujuannya tidak termasuk "membuatnya berorientasi objek" :
(Dia menjelaskan mengapa warisan dan virtual - alias desain berorientasi objek "pada dasarnya cacat dan tidak boleh digunakan" dalam sisa wawancara).
Begitu Stepanov mempresentasikan perpustakaannya ke Stroustrup, Stroustrup dan yang lainnya melakukan upaya herculean untuk memasukkannya ke dalam standar ISO C ++ (wawancara yang sama):
sumber
Jawabannya ditemukan dalam wawancara ini dengan Stepanov, penulis STL:
sumber
Mengapa desain OOP murni untuk Perpustakaan Struktur Data & Algoritma akan lebih baik?! OOP bukanlah solusi untuk segala hal.
IMHO, STL adalah perpustakaan paling elegan yang pernah saya lihat :)
untuk pertanyaan anda,
Anda tidak perlu polimorfisme runtime, ini merupakan keuntungan bagi STL sebenarnya untuk mengimplementasikan Perpustakaan menggunakan polimorfisme statis, yang berarti efisiensi. Cobalah untuk menulis Urutkan atau Jarak generik atau algoritma apa pun yang berlaku untuk SEMUA wadah! Sortir di Java Anda akan memanggil fungsi yang dinamis melalui n-level yang akan dieksekusi!
Anda perlu hal bodoh seperti Boxing dan Unboxing untuk menyembunyikan asumsi buruk dari apa yang disebut bahasa OOP Murni.
Satu-satunya masalah yang saya lihat dengan STL, dan template secara umum adalah pesan kesalahan yang mengerikan. Yang akan dipecahkan menggunakan Konsep di C ++ 0X.
Membandingkan STL dengan Koleksi di Jawa Seperti Membandingkan Taj Mahal dengan rumah saya :)
sumber
static_assert
mungkin.Saya pikir Anda salah paham tentang penggunaan konsep yang dimaksudkan oleh templat. Forward Iterator, misalnya, adalah konsep yang sangat jelas. Untuk menemukan ekspresi yang harus valid agar kelas menjadi Forward Iterator, dan semantik mereka termasuk kompleksitas komputasi, Anda melihat standar atau di http://www.sgi.com/tech/stl/ForwardIterator.html (Anda harus mengikuti tautan ke Input, Output, dan Trivial Iterator untuk melihat semuanya).
Dokumen itu adalah antarmuka yang sangat baik, dan "rincian sebenarnya dari konsep" didefinisikan di sana. Mereka tidak didefinisikan oleh implementasi Forward Iterators, dan mereka juga tidak didefinisikan oleh algoritma yang menggunakan Forward Iterators.
Perbedaan dalam cara antarmuka ditangani antara STL dan Java tiga kali lipat:
1) STL mendefinisikan ekspresi yang valid menggunakan objek, sedangkan Java mendefinisikan metode yang harus dipanggil pada objek. Tentu saja ekspresi yang valid mungkin merupakan panggilan metode (fungsi anggota), tetapi tidak harus demikian.
2) Java interface adalah objek runtime, sedangkan konsep STL tidak terlihat saat runtime bahkan dengan RTTI.
3) Jika Anda gagal membuat valid ekspresi yang valid diperlukan untuk konsep STL, Anda mendapatkan kesalahan kompilasi yang tidak ditentukan ketika Anda instantiate beberapa template dengan tipe. Jika Anda gagal menerapkan metode yang diperlukan dari antarmuka Java, Anda mendapatkan kesalahan kompilasi tertentu yang mengatakannya.
Bagian ketiga ini adalah jika Anda suka jenis (compile-time) "duck typing": antarmuka bisa tersirat. Di Jawa, antarmuka agak eksplisit: kelas "adalah" Iterable jika dan hanya jika dikatakan mengimplementasikan Iterable. Kompiler dapat memeriksa bahwa tanda tangan dari semua metodenya ada dan benar, tetapi semantiknya masih implisit (mis. Keduanya didokumentasikan atau tidak, tetapi hanya lebih banyak kode (unit test) yang dapat memberi tahu Anda apakah implementasinya benar).
Di C ++, seperti di Python, semantik dan sintaksis adalah implisit, meskipun di C ++ (dan di Python jika Anda mendapatkan preprocessor pengetikan kuat) Anda memang mendapatkan bantuan dari kompiler. Jika seorang programmer membutuhkan deklarasi antarmuka eksplisit seperti Java oleh kelas pelaksana, maka pendekatan standarnya adalah menggunakan ciri-ciri tipe (dan multiple inheritance dapat mencegah hal ini menjadi terlalu bertele-tele). Apa yang kurang, dibandingkan dengan Java, adalah satu template yang dapat saya instantiate dengan tipe saya, dan yang akan mengkompilasi jika dan hanya jika semua ekspresi yang diperlukan valid untuk tipe saya. Ini akan memberi tahu saya apakah saya sudah menerapkan semua bit yang diperlukan, "sebelum saya menggunakannya". Itu kenyamanan, tapi itu bukan inti dari OOP (dan itu masih tidak menguji semantik,
STL mungkin atau mungkin tidak cukup OO untuk selera Anda, tetapi tentu saja memisahkan antarmuka secara bersih dari implementasi. Itu tidak memiliki kemampuan Java untuk melakukan refleksi atas antarmuka, dan melaporkan pelanggaran persyaratan antarmuka secara berbeda.
Secara pribadi saya berpikir bahwa tipe implisit adalah kekuatan, ketika digunakan dengan tepat. Algoritme mengatakan apa yang dilakukannya dengan parameter templatnya, dan implementer memastikan hal-hal itu berfungsi: itu adalah penyebut yang umum dari apa yang harus dilakukan "antarmuka". Selain itu dengan STL, Anda tidak mungkin menggunakan, katakanlah,
std::copy
berdasarkan menemukan deklarasi maju dalam file header. Pemrogram harus mencari tahu apa fungsi yang diambil berdasarkan dokumentasinya, bukan hanya pada tanda tangan fungsi. Ini benar dalam C ++, Python, atau Java. Ada batasan pada apa yang dapat dicapai dengan mengetik dalam bahasa apa pun, dan mencoba menggunakan mengetik untuk melakukan sesuatu yang tidak dilakukannya (periksa semantik) akan menjadi kesalahan.Yang mengatakan, algoritma STL biasanya memberi nama parameter template mereka dengan cara yang menjelaskan konsep apa yang diperlukan. Namun ini adalah untuk memberikan informasi tambahan yang berguna di baris pertama dokumentasi, bukan untuk membuat deklarasi maju lebih informatif. Ada lebih banyak hal yang perlu Anda ketahui daripada yang dapat diringkas dalam jenis parameter, sehingga Anda harus membaca dokumen. (Misalnya dalam algoritma yang mengambil rentang input dan output iterator, kemungkinan iterator output membutuhkan "ruang" yang cukup untuk sejumlah output berdasarkan pada ukuran rentang input dan mungkin nilai-nilai di dalamnya. Coba ketikkan itu dengan kuat. )
Inilah Bjarne pada antarmuka yang dinyatakan secara eksplisit: http://www.artima.com/cppsource/cpp0xP.html
Melihatnya sebaliknya, dengan mengetik bebek Anda dapat mengimplementasikan antarmuka tanpa mengetahui bahwa antarmuka ada. Atau seseorang dapat menulis antarmuka dengan sengaja sehingga kelas Anda mengimplementasikannya, setelah berkonsultasi dengan dokumen Anda untuk memastikan bahwa mereka tidak meminta apa pun yang belum Anda lakukan. Itu fleksibel.
sumber
std
perpustakaan yang gagal mencocokkan konsep biasanya "tidak terbentuk, tidak ada diagnostik yang diperlukan"."OOP bagi saya hanya berarti pengiriman pesan, retensi lokal dan perlindungan dan menyembunyikan proses negara, dan sangat mengikat semua hal. Ini dapat dilakukan di Smalltalk dan di LISP. Mungkin ada sistem lain di mana ini mungkin, tetapi Saya tidak menyadarinya. " - Alan Kay, pencipta Smalltalk.
C ++, Java, dan sebagian besar bahasa lainnya semuanya cukup jauh dari OOP klasik. Yang mengatakan, berdebat untuk ideologi tidak terlalu produktif. C ++ tidak murni dalam arti apa pun, sehingga mengimplementasikan fungsionalitas yang tampaknya masuk akal secara pragmatis saat itu.
sumber
STL dimulai dengan maksud untuk menyediakan perpustakaan besar yang mencakup algoritma yang paling umum digunakan - dengan target perilaku dan kinerja yang konsisten . Template datang sebagai faktor kunci untuk membuat implementasi dan target tersebut layak.
Hanya untuk memberikan referensi lain:
Al Stevens Wawancara Alex Stepanov, pada bulan Maret 1995 dari DDJ:
Stepanov menjelaskan pengalaman kerja dan pilihannya dibuat menuju perpustakaan algoritma yang besar, yang akhirnya berkembang menjadi STL.
sumber
Masalah mendasar dengan
adalah bagaimana Anda mendapatkan jenis barang yang dikembalikan oleh iterator dengan aman? Dengan templat, ini dilakukan untuk Anda pada waktu kompilasi.
sumber
Untuk sesaat, mari kita pikirkan pustaka standar sebagai basis data koleksi dan algoritma.
Jika Anda telah mempelajari sejarah basis data, Anda pasti tahu bahwa pada awalnya, basis data kebanyakan "hierarkis". Basis data hierarkis berhubungan sangat erat dengan OOP klasik - khususnya, varietas warisan tunggal, seperti yang digunakan oleh Smalltalk.
Seiring waktu, menjadi jelas bahwa database hierarkis dapat digunakan untuk memodelkan hampir semua hal, tetapi dalam beberapa kasus model warisan tunggal cukup membatasi. Jika Anda memiliki pintu kayu, itu berguna untuk dapat melihatnya sebagai pintu, atau sebagai bagian dari beberapa bahan baku (baja, kayu, dll.)
Jadi, mereka menemukan database model jaringan. Database model jaringan berhubungan sangat erat dengan multiple inheritance. C ++ mendukung banyak pewarisan sepenuhnya, sementara Java mendukung bentuk terbatas (Anda dapat mewarisi dari satu kelas, tetapi juga dapat mengimplementasikan sebanyak mungkin antarmuka yang Anda inginkan).
Kedua model hierarkis dan database model jaringan sebagian besar telah memudar dari penggunaan tujuan umum (meskipun beberapa tetap dalam ceruk yang cukup spesifik). Untuk sebagian besar tujuan, mereka telah digantikan oleh database relasional.
Sebagian besar alasan database relasional mengambil alih adalah fleksibilitas. Model relasional secara fungsional merupakan superset dari model jaringan (yang, pada gilirannya, merupakan superset dari model hierarkis).
C ++ sebagian besar mengikuti jalur yang sama. Korespondensi antara pewarisan tunggal dan model hierarkis dan antara pewarisan berganda dan model jaringan cukup jelas. Korespondensi antara templat C ++ dan model hierarkis mungkin kurang jelas, tapi tetap cocok.
Saya belum melihat bukti formal tentang hal itu, tetapi saya percaya kemampuan template adalah superset dari yang disediakan oleh banyak pewarisan (yang jelas merupakan superset dari inerhitance tunggal). Bagian yang sulit adalah bahwa sebagian besar templat terikat secara statis - yaitu, semua pengikatan terjadi pada waktu kompilasi, bukan waktu berjalan. Dengan demikian, sebuah bukti formal bahwa warisan memberikan superset kemampuan warisan mungkin agak sulit dan kompleks (atau bahkan mungkin tidak mungkin).
Bagaimanapun, saya pikir itulah alasan sebenarnya C ++ tidak menggunakan warisan untuk wadahnya - tidak ada alasan nyata untuk melakukannya, karena warisan hanya menyediakan sebagian dari kemampuan yang disediakan oleh templat. Karena template pada dasarnya suatu keharusan dalam beberapa kasus, mereka mungkin juga digunakan hampir di mana-mana.
sumber
Bagaimana Anda melakukan perbandingan dengan ForwardIterator *? Yaitu, bagaimana Anda memeriksa apakah barang yang Anda miliki adalah yang Anda cari, atau Anda telah melewatinya?
Sebagian besar waktu, saya akan menggunakan sesuatu seperti ini:
yang berarti saya tahu bahwa saya menunjuk ke MyType, dan saya tahu bagaimana membandingkannya. Meskipun terlihat seperti templat, itu sebenarnya bukan kata kunci (tidak ada templat).
sumber
Pertanyaan ini memiliki banyak jawaban. Juga harus disebutkan bahwa templat mendukung desain terbuka. Dengan keadaan saat ini dari bahasa pemrograman berorientasi objek, kita harus menggunakan pola pengunjung ketika berhadapan dengan masalah seperti itu, dan OOP sejati harus mendukung banyak pengikatan dinamis. Lihat Buka Multi-Metode untuk C ++, P. Pirkelbauer, et.al. untuk bacaan yang sangat menarik.
Poin lain yang menarik dari template adalah mereka dapat digunakan untuk polimorfisme runtime juga. Sebagai contoh
Perhatikan bahwa fungsi ini juga akan berfungsi jika
Value
merupakan semacam vektor ( bukan std :: vector, yang harus dipanggilstd::dynamic_array
untuk menghindari kebingungan)Jika
func
kecil, fungsi ini akan mendapat banyak manfaat dari inlining. Contoh penggunaanDalam hal ini, Anda harus tahu jawaban pastinya (2.718 ...), tetapi mudah untuk membuat ODE sederhana tanpa solusi dasar (Petunjuk: gunakan polinomial dalam y).
Sekarang, Anda memiliki ekspresi yang besar
func
, dan Anda menggunakan ODE solver di banyak tempat, sehingga file executable Anda akan tercemar dengan instantiasi templat di mana-mana. Apa yang harus dilakukan? Hal pertama yang perlu diperhatikan adalah fungsi pointer berfungsi. Kemudian Anda ingin menambahkan currying sehingga Anda menulis antarmuka dan instantiasi eksplisitTetapi instantiasi di atas hanya berfungsi
double
, mengapa tidak menulis antarmuka sebagai templat:dan berspesialisasi untuk beberapa jenis nilai umum:
Jika fungsi telah dirancang di sekitar antarmuka terlebih dahulu, maka Anda akan dipaksa untuk mewarisi dari ABC itu. Sekarang Anda memiliki opsi ini, serta penunjuk fungsi, lambda, atau objek fungsi lainnya. Kuncinya di sini adalah bahwa kita harus memiliki
operator()()
, dan kita harus dapat menggunakan beberapa operator aritmatika pada tipe kembalinya. Dengan demikian, mesin template akan rusak dalam hal ini jika C ++ tidak memiliki operator yang berlebihan.sumber
Konsep memisahkan antarmuka dari antarmuka dan mampu menukar implementasi tidak intrinsik dengan Pemrograman Berorientasi Objek. Saya percaya ini adalah ide yang diarsir dalam Pengembangan Berbasis Komponen seperti Microsoft COM. (Lihat jawaban saya pada Apa itu Pengembangan Komponen-Didorong?) Tumbuh dan belajar C ++, orang dihipnotis warisan dan polimorfisme. Tidak sampai tahun 90-an orang mulai mengatakan "Program ke 'antarmuka', bukan 'implementasi'" dan "Komposisi objek 'Favor' di atas 'warisan kelas'." (Keduanya dikutip dari GoF).
Kemudian Java hadir dengan pengumpul dan
interface
kata kunci sampah bawaan , dan tiba-tiba menjadi praktis untuk benar-benar memisahkan antarmuka dan implementasi. Sebelum Anda menyadarinya, idenya menjadi bagian dari OO. C ++, templat, dan STL ada sebelum ini.sumber