Mengapa C ++ STL sangat berbasis pada templat? (dan bukan pada * antarmuka *)

211

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)

OB OB
sumber
4
Anda mungkin memeriksa stackoverflow.com/questions/31693/… . Jawaban yang diterima adalah penjelasan yang sangat baik tentang template apa yang Anda tawarkan daripada obat generik.
James McMahon
6
@ Jonas: Itu tidak masuk akal. Batasan pada biaya cache siklus jam, itulah mengapa itu penting. Pada akhirnya, itu adalah siklus clock, bukan cache, yang menentukan kinerja. Memori, dan cache hanya penting sejauh ini memengaruhi siklus jam yang dihabiskan. Apalagi percobaannya bisa dilakukan dengan mudah. Bandingkan, katakanlah, std :: for_Each dipanggil dengan argumen functor, dengan pendekatan OOP / vtable yang setara. Perbedaan kinerja sangat mengejutkan . Itulah sebabnya versi templat digunakan.
jalf
7
dan tidak ada alasan mengapa kode redundan akan mengisi icache. Jika saya instantiate vektor <char> dan vektor <int> dalam program saya, mengapa kode <char> vektor harus dimuat ke icache ketika saya sedang memproses vektor <int>? Sebenarnya, kode untuk vektor <int> dipangkas karena tidak harus menyertakan kode untuk casting, vtables dan tipuan.
jalf
3
Alex Stepanov menjelaskan mengapa warisan dan kesetaraan tidak bermain bersama dengan baik.
fredoverflow
6
@BerndJendrissek: Uhm, tutup, tapi jangan dirimu sendiri. Ya, lebih banyak biaya kode dalam hal bandwidth memori dan penggunaan cache jika itu benar-benar digunakan . Tetapi tidak ada alasan khusus untuk mengharapkan vector<int>dan vector<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 Instansiasi vector<int>yang memerlukan vector<char>kode untuk dimuat atau dieksekusi.
jalf

Jawaban:

607

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 vectortajuk, tanpa juga masuk iteratoratau bahkan iostreamditarik? 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?

jalf
sumber
22
Langgan yang bagus, tapi saya ingin menyoroti satu detail. STL bukan "produk" dari C ++. Faktanya, STL, sebagai sebuah konsep, ada sebelum C ++, dan C ++ kebetulan merupakan bahasa yang efisien yang memiliki (hampir) kekuatan yang cukup untuk pemrograman generik, jadi STL ditulis dalam C ++.
Igor Krivokon
17
Karena komentar terus memunculkannya, ya, saya sadar bahwa nama STL ambigu. Tapi saya tidak bisa memikirkan nama yang lebih baik untuk "bagian dari perpustakaan standar C ++ yang dimodelkan pada STL". Nama de-facto untuk bagian perpustakaan standar itu hanya "STL", meskipun itu sangat tidak akurat. :) Selama orang tidak menggunakan STL sebagai nama untuk seluruh perpustakaan standar (termasuk IOStreams dan header stdlib C), saya senang. :)
jalf
5
@einpoklum Dan apa yang sebenarnya Anda dapatkan dari kelas dasar abstrak? Ambil std::setsebagai contoh. Itu tidak mewarisi dari kelas dasar abstrak. Bagaimana itu membatasi penggunaan Anda std::set? Apakah ada sesuatu yang tidak dapat Anda lakukan dengan a std::setkarena tidak mewarisi dari kelas dasar abstrak?
fredoverflow
22
@einpoklum, silakan lihat bahasa Smalltalk, yang dirancang oleh Alan Kay sebagai bahasa OOP ketika ia menemukan istilah OOP. Itu tidak memiliki antarmuka. OOP bukan tentang antarmuka atau kelas dasar abstrak. Apakah Anda akan mengatakan bahwa "Java, yang tidak seperti apa yang ditemukan oleh penemu istilah OOP lebih dari OOP daripada C ++ yang juga tidak seperti yang ada dalam pikiran penemu istilah OOP"? Apa yang ingin Anda katakan adalah "C ++ tidak cukup seperti Java untuk seleraku". Itu adil, tetapi tidak ada hubungannya dengan OOP.
jalf
8
@MasonWheeler jika jawaban ini adalah omong kosong terang-terangan Anda tidak akan melihat ratusan pengembang di seluruh dunia memilih +1 untuk ini dengan hanya tiga orang melakukan sebaliknya
panda-34
88

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.

Tyler McHenry
sumber
4
"dan dengan C ++ 0x beberapa hal bahkan dapat diprogram secara fungsional" - dapat diprogram secara fungsional tanpa fitur-fitur tersebut, hanya secara lebih kasar.
Jonas Kölker
3
@ Tyler Memang jika Anda membatasi C ++ ke OOP murni, Anda akan dibiarkan dengan Objective-C.
Justicle
@TylerMcHenry: Baru saja menanyakan hal ini , saya menemukan jawaban yang sama dengan Anda! Hanya satu poin. Saya berharap Anda akan menambahkan fakta bahwa Perpustakaan Standar tidak dapat digunakan untuk menulis kode Berorientasi Objek.
einpoklum
74

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" :

Itu adalah poin mendasar: algoritma didefinisikan pada struktur aljabar. Butuh beberapa tahun bagi saya untuk menyadari bahwa Anda harus memperluas gagasan struktur dengan menambahkan persyaratan kompleksitas pada aksioma biasa. ... Saya percaya bahwa teori iterator sama pentingnya dengan Ilmu Komputer seperti halnya teori cincin atau ruang Banach adalah pusat dari Matematika. Setiap kali saya melihat algoritma saya akan mencoba menemukan struktur yang didefinisikan. Jadi yang ingin saya lakukan adalah mendeskripsikan algoritma secara umum. Itu yang ingin saya lakukan. Saya dapat menghabiskan satu bulan bekerja pada algoritma yang terkenal mencoba untuk menemukan representasi generiknya. ...

STL, setidaknya bagi saya, merupakan satu-satunya cara pemrograman mungkin. Memang, sangat berbeda dari pemrograman C ++ seperti yang disajikan dan masih disajikan di sebagian besar buku teks. Tapi, Anda tahu, saya tidak mencoba program dalam C ++, saya mencoba mencari cara yang tepat untuk berurusan dengan perangkat lunak. ...

Saya memiliki banyak kesalahan awal. Sebagai contoh, saya menghabiskan waktu bertahun-tahun mencoba menemukan beberapa kegunaan untuk warisan dan virtual, sebelum saya mengerti mengapa mekanisme itu pada dasarnya cacat dan tidak boleh digunakan. Saya sangat senang bahwa tidak ada yang bisa melihat semua langkah perantara - kebanyakan dari mereka sangat konyol.

(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):

Dukungan Bjarne Stroustrup sangat penting. Bjarne benar-benar menginginkan STL dalam standar dan jika Bjarne menginginkan sesuatu, dia mendapatkannya. ... Dia bahkan memaksa saya untuk membuat perubahan di STL yang saya tidak akan pernah buat untuk orang lain ... dia adalah orang yang paling berpikiran tunggal yang saya tahu. Dia menyelesaikan sesuatu. Butuh beberapa saat baginya untuk memahami apa itu STL, tetapi ketika dia melakukannya, dia siap untuk mendorongnya. Dia juga berkontribusi pada STL dengan mendukung pandangan bahwa lebih dari satu cara pemrograman itu valid - tidak terkecuali tanpa henti dan hype selama lebih dari satu dekade, dan mengejar kombinasi fleksibilitas, efisiensi, kelebihan beban, dan keamanan jenis di templat yang memungkinkan STL. Saya ingin menyatakan dengan jelas bahwa Bjarne adalah perancang bahasa yang paling unggul dari generasi saya.

Max Lybbert
sumber
2
Wawancara menarik. Cukup yakin saya sudah membacanya sebelum beberapa waktu yang lalu, tetapi pasti layak untuk ditinjau kembali. :)
jalf
3
Salah satu wawancara paling menarik tentang pemrograman yang pernah saya baca. Meskipun itu membuat saya haus untuk lebih detail ...
Felixyz
Banyak keluhan yang ia buat tentang bahasa seperti Java ("Anda tidak dapat menulis maks generik () di Jawa yang mengambil dua argumen dari beberapa jenis dan memiliki nilai pengembalian dari jenis yang sama") hanya relevan dengan versi yang sangat awal bahasa, sebelum obat generik ditambahkan. Bahkan sejak awal, diketahui bahwa obat generik pada akhirnya akan ditambahkan, meskipun (setelah sintaks / semantik yang berhasil ditemukan), sehingga kritiknya sebagian besar tidak berdasar. Ya, obat generik dalam beberapa bentuk diperlukan untuk menjaga keamanan jenis dalam bahasa yang diketik secara statis, tetapi tidak, itu tidak membuat OO tidak berharga.
Some Guy
1
@OmeGuy Mereka bukan keluhan tentang Java. Dia berbicara tentang " the" standard "OO programming SmallTalk atau, katakanlah, Java ". Wawancara ini berasal dari akhir tahun 90an (ia menyebutkan bekerja di SGI, yang ia tinggalkan pada tahun 2000 untuk bekerja di AT&T). Generik hanya ditambahkan ke Java pada tahun 2004 dalam versi 1.5 dan mereka merupakan penyimpangan dari model OO "standar".
melpomene
24

Jawabannya ditemukan dalam wawancara ini dengan Stepanov, penulis STL:

Iya. STL tidak berorientasi objek. Saya pikir bahwa orientasi objek hampir sama bohongnya dengan Kecerdasan Buatan. Saya belum melihat potongan kode yang menarik yang datang dari orang-orang OO ini.

StackedCrooked
sumber
Permata yang bagus; Anda tahu tahun berapa ini?
Kos
2
@Kos, menurut web.archive.org/web/20000607205939/http://www.stlport.org/... versi pertama dari halaman tertaut adalah dari 7 Juni 2001. Halaman itu sendiri di bagian bawah mengatakan Copyright 2001- 2008
alfC
@Kos Stepanov menyebutkan bekerja di SGI pada jawaban pertama. Dia meninggalkan SGI pada Mei 2000, jadi mungkin saja wawancaranya lebih tua dari itu.
melpomene
18

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 :)

AraK
sumber
12
Apa, Taj Mahal kecil dan elegan, dan rumah Anda seukuran gunung, dan berantakan total? ;)
jalf
Konsep bukan bagian dari c ++ 0x lagi. Beberapa pesan kesalahan mungkin dapat dihindari menggunakan static_assertmungkin.
KitsuneYMG
GCC 4.6 telah memperbaiki pesan kesalahan templat, dan saya percaya bahwa 4.7+ bahkan lebih baik dengannya.
David Stone
Konsep pada dasarnya adalah "antarmuka" yang diminta OP. Satu-satunya perbedaan adalah bahwa "warisan" dari suatu Konsep adalah implisit (jika kelas memiliki semua fungsi anggota yang tepat, secara otomatis merupakan subtipe dari Konsep) daripada eksplisit (kelas Java harus secara eksplisit menyatakan bahwa ia mengimplementasikan antarmuka) . Namun, subtyping implisit dan eksplisit adalah OO yang valid, dan beberapa bahasa OO memiliki warisan implisit yang bekerja seperti halnya Konsep. Jadi apa yang dikatakan di sini pada dasarnya adalah "OO menyebalkan: gunakan templat. Tapi templat memiliki masalah, jadi gunakan Konsep (yang merupakan OO)."
Some Guy
11

tipe templated seharusnya mengikuti "konsep" (Input Iterator, Forward Iterator, dll ...) di mana rincian sebenarnya dari konsep tersebut ditentukan seluruhnya oleh implementasi fungsi templat / kelas, dan bukan oleh kelas dari tipe digunakan dengan template, yang agak anti-penggunaan OOP.

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.

Anda dapat memberi tahu fungsinya ... mengharapkan Forward Iterator hanya dengan melihat definisinya, di mana Anda perlu melihat implementasinya atau dokumentasi untuk ...

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::copyberdasarkan 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

Dalam generik, argumen harus dari kelas yang berasal dari antarmuka (setara C ++ untuk antarmuka adalah kelas abstrak) yang ditentukan dalam definisi generik. Itu berarti bahwa semua tipe argumen umum harus masuk ke dalam hierarki. Hal itu menimbulkan kendala yang tidak perlu pada desain yang membutuhkan tinjauan ke masa depan yang tidak masuk akal dari pihak pengembang. Misalnya, jika Anda menulis generik dan saya mendefinisikan kelas, orang tidak dapat menggunakan kelas saya sebagai argumen untuk generik Anda kecuali saya tahu tentang antarmuka yang Anda tentukan dan telah mengambil kelas saya dari itu. Itu kaku.

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.

Steve Jessop
sumber
Pada antarmuka yang dinyatakan secara eksplisit, dua kata: ketik kelas. (Yang sudah apa yang dimaksud Stepanov dengan "konsep".)
pyon
"Jika Anda gagal untuk membuat valid ekspresi valid yang diperlukan untuk konsep STL, Anda mendapatkan kesalahan kompilasi yang tidak ditentukan ketika Anda instantiate beberapa template dengan tipe." - itu salah. Mengirimkan sesuatu ke stdperpustakaan yang gagal mencocokkan konsep biasanya "tidak terbentuk, tidak ada diagnostik yang diperlukan".
Yakk - Adam Nevraumont
Benar, saya bermain cepat dan longgar dengan istilah "valid". Saya hanya bermaksud jika kompiler tidak dapat mengkompilasi salah satu ekspresi yang diperlukan, maka ia akan melaporkan sesuatu.
Steve Jessop
8

"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.

Ben Hughes
sumber
7

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.

Beri tahu kami sesuatu tentang minat jangka panjang Anda pada pemrograman generik

..... Kemudian saya ditawari pekerjaan di Bell Laboratories yang bekerja di grup C ++ di perpustakaan C ++. Mereka bertanya kepada saya apakah saya bisa melakukannya di C ++. Tentu saja, saya tidak tahu C ++ dan, tentu saja, saya bilang saya bisa. Tapi saya tidak bisa melakukannya di C ++, karena pada tahun 1987 C ++ tidak memiliki template, yang sangat penting untuk mengaktifkan gaya pemrograman ini. Warisan adalah satu-satunya mekanisme untuk mendapatkan kedermawanan dan itu tidak cukup.

Bahkan sekarang warisan C ++ tidak banyak digunakan untuk pemrograman generik. Mari kita bahas mengapa. Banyak orang telah berusaha menggunakan warisan untuk mengimplementasikan struktur data dan kelas kontainer. Seperti yang kita ketahui sekarang, ada beberapa upaya yang berhasil. C ++ inheritance, dan gaya pemrograman yang terkait dengannya secara dramatis terbatas. Tidak mungkin untuk mengimplementasikan desain yang mencakup hal sepele seperti kesetaraan menggunakannya. Jika Anda mulai dengan kelas dasar X pada akar hierarki Anda dan mendefinisikan operator kesetaraan virtual pada kelas ini yang mengambil argumen tipe X, maka turunkan kelas Y dari kelas X. Apa antarmuka dari kesetaraan? Ia memiliki kesetaraan yang membandingkan Y dengan X. Menggunakan hewan sebagai contoh (orang-orang OO suka binatang), mendefinisikan mamalia dan memperoleh jerapah dari mamalia. Kemudian tentukan pasangan fungsi anggota, di mana hewan kawin dengan binatang dan mengembalikan binatang. Kemudian Anda memperoleh jerapah dari hewan dan, tentu saja, ia memiliki fungsi jodoh di mana jerapah kawin dengan hewan dan mengembalikan seekor hewan. Ini jelas bukan yang Anda inginkan. Sementara kawin mungkin tidak terlalu penting bagi programmer C ++, kesetaraan adalah. Saya tidak tahu algoritma tunggal di mana persamaan jenis tidak digunakan.

yowkee
sumber
5

Masalah mendasar dengan

void MyFunc(ForwardIterator *I);

adalah bagaimana Anda mendapatkan jenis barang yang dikembalikan oleh iterator dengan aman? Dengan templat, ini dilakukan untuk Anda pada waktu kompilasi.


sumber
1
Yah, saya juga: 1. Jangan mencoba untuk mendapatkannya, karena saya sedang menulis kode generik. Atau, 2. Dapatkan menggunakan mekanisme refleksi apa pun yang ditawarkan C ++ hari ini.
einpoklum
2

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.

Jerry Coffin
sumber
0

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:

void MyFunc(ForwardIterator<MyType>& i)

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).

Tanktalus
sumber
Anda hanya dapat menggunakan <,> dan = operator dari jenis dan tidak tahu apa yang mereka adalah (meskipun kekuatan ini bukan apa yang Anda maksud)
lhahne
Tergantung pada konteksnya, itu mungkin tidak masuk akal, atau mereka bisa berfungsi dengan baik. Sulit untuk mengatakan tanpa mengetahui lebih banyak tentang MyType, yang, mungkin, yang dilakukan pengguna, dan kami tidak.
Tanktalus
0

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

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;
    }

Perhatikan bahwa fungsi ini juga akan berfungsi jika Valuemerupakan semacam vektor ( bukan std :: vector, yang harus dipanggil std::dynamic_arrayuntuk menghindari kebingungan)

Jika funckecil, fungsi ini akan mendapat banyak manfaat dari inlining. Contoh penggunaan

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

Dalam 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 eksplisit

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);

Tetapi instantiasi di atas hanya berfungsi double, mengapa tidak menulis antarmuka sebagai templat:

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

dan berspesialisasi untuk beberapa jenis nilai umum:

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)

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.

pengguna877329
sumber
-1

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 interfacekata 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.

Eugene Yokota
sumber
Setuju bahwa antarmuka bukan hanya OO. Tetapi kemampuan polimorfisme dalam sistem tipe (itu di Simula di tahun 60-an). Antarmuka modul ada di Modula-2 dan Ada, tetapi ini beroperasi dalam sistem tipe yang berbeda saya pikir.
andygavin