Mengapa konsep (pemrograman generik) dipahami ketika kita sudah memiliki kelas dan antarmuka?

8

Juga di stackoverflow.com :

Saya mengerti bahwa konsep STL harus ada, dan itu akan konyol untuk memanggil mereka "kelas" atau "antarmuka" ketika sebenarnya mereka hanya mendokumentasikan konsep (manusia) dan tidak dapat diterjemahkan ke dalam kode C ++ pada saat itu, tetapi ketika diberi kesempatan untuk memperluas bahasa untuk mengakomodasi konsep, mengapa mereka tidak hanya memodifikasi kemampuan kelas dan / atau memperkenalkan antarmuka?

Bukankah konsep sangat mirip dengan antarmuka (kelas abstrak 100% tanpa data)? Dengan melihatnya, menurut saya antarmuka hanya kurang mendukung aksioma, tetapi mungkin aksioma dapat dimasukkan ke dalam antarmuka C ++ (mempertimbangkan adopsi antarmuka secara hipotetis dalam C ++ untuk mengambil alih konsep), bukan? Saya pikir bahkan konsep otomatis dapat dengan mudah ditambahkan ke antarmuka C ++ (antarmuka otomatis LessThanComparable, ada orang?).

Bukankah concept_map sangat mirip dengan pola Adaptor? Jika semua metode inline, adaptor pada dasarnya tidak ada di luar waktu kompilasi; kompiler hanya mengganti panggilan ke antarmuka dengan versi inline, memanggil objek target secara langsung selama runtime.

Saya pernah mendengar tentang sesuatu yang disebut Pemrograman Berorientasi Objek Statis, yang pada dasarnya berarti secara efektif menggunakan kembali konsep orientasi objek dalam pemrograman generik, sehingga memungkinkan penggunaan sebagian besar kekuatan OOP tanpa menimbulkan overhead eksekusi. Mengapa ide ini tidak dipertimbangkan lebih lanjut?

Saya harap ini cukup jelas. Saya dapat menulis ulang ini jika Anda pikir saya tidak; Kabari saja.

Gui Prá
sumber

Jawaban:

13

Jawaban singkat: Anda sedang mencampur konsep sebelum waktu kompilasi dan waktu kompilasi yang memiliki kesamaan dalam tujuannya. Antarmuka (kelas abstrak dan semua implementasi paradigma objek-orientasi) diinformasikan pada waktu kompilasi . Konsep adalah ide yang sama tetapi dalam konteks pemrograman generik yang dalam C ++ terjadi SEBELUM waktu kompilasi . Kami belum memiliki fitur terakhir.

Tapi saya akan jelaskan dari awal.


Jawaban panjang:

Bahkan, Konsep hanyalah informasi bahasa dan "membuat lebih mudah bagi programmer" dari sesuatu yang sudah ada dalam bahasa, yang bisa Anda sebut "mengetik bebek".

Ketika Anda meneruskan suatu tipe ke fungsi templat, itu adalah fungsi generik yang darinya kompiler akan menghasilkan kode nyata (sebaris) ketika dipanggil, tipe itu perlu memiliki beberapa properti (ciri?) Yang akan digunakan dalam kode templat. Jadi ini adalah ide untuk mengetik bebek tetapi itu semua dihasilkan dan dilakukan pada waktu kompilasi .

Apa yang terjadi ketika tipe tidak memiliki properti yang diperlukan?

Nah, kompiler akan tahu bahwa ada masalah hanya setelah kode yang dihasilkan dari template dikompilasi dan gagal. Itu berarti bahwa kesalahan yang akan dihasilkan akan menjadi kesalahan di dalam kode templat, yang akan ditampilkan kepada programmer sebagai kesalahannya. Juga, kesalahan akan memiliki banyak informasi karena informasi meta yang disediakan dalam hal pembuatan kode templat, untuk mengetahui instantiasi templat mana yang sedang kita bicarakan.

Beberapa masalah dengan itu: pertama, sebagian besar waktu, kode template adalah kode perpustakaan dan kebanyakan programmer adalah pengguna kode perpustakaan, bukan penulis kode perpustakaan. Itu berarti bahwa jenis kesalahan samar ini benar-benar sulit untuk dipahami ketika Anda tidak mengerti bagaimana perpustakaan ditulis (bukan hanya desain, bagaimana itu benar-benar dilaksanakan). Masalah kedua adalah bahwa bahkan ketika programmer menulis kode templat, alasan kegagalan mungkin masih tidak jelas karena kompiler akan dapat mengatakan bahwa ada masalah terlambat: ketika kode yang dihasilkan sedang dikompilasi. Jika masalahnya relatif terhadap tipe properti, maka ia harus memeriksanya bahkan sebelum membuat kode.

Itulah yang dibolehkan Konsep (dan dirancang untuk): untuk memungkinkan pemrogram (kode generik) untuk menentukan properti tipe yang dilewatkan sebagai parameter templat dan kemudian memungkinkan kompiler untuk memberikan kesalahan eksplisit jika jenis yang disediakan tidak memenuhi Persyaratan.

Setelah pemeriksaan berhasil, kode akan dihasilkan dari template dan kemudian dikompilasi, tentunya berhasil.

Semua pengecekan Konsep terjadi secara eksklusif sebelum waktu kompilasi . Itu memeriksa jenis sendiri, bukan jenis objek . Tidak ada objek sebelum waktu kompilasi.

Sekarang, tentang "antarmuka".

Saat Anda membuat tipe basis abstrak atau virtual, Anda mengizinkan kode menggunakannya untuk memanipulasi objek tipe anak tanpa mengetahui implementasinya yang sebenarnya. Untuk menegakkan ini, tipe dasar mengekspos anggota yang virtual dan mungkin (atau harus) kelebihan beban oleh tipe anak.

Itu berarti bahwa kompilator dapat memeriksa pada waktu kompilasi bahwa semua objek yang dilewatkan ke fungsi yang memerlukan referensi ke kelas dasar harus 1. menjadi salah satu dari tipe anak dari kelas dasar, 2. bahwa tipe anak harus memiliki implementasi dari fungsi virtual murni dideklarasikan di kelas dasar jika ada.

Jadi pada saat kompilasi , kompiler akan memeriksa antarmuka tipe objek dan melaporkan jika ada sesuatu yang hilang.

Itu ide yang sama dari Konsep, tetapi terjadi terlambat , seperti yang dikatakan dalam deskripsi Konsep. Itu terjadi pada waktu kompilasi. Kami tidak dalam kode generik (kode templat), kami setelah diproses dan sudah terlambat untuk memeriksa apakah jenis memenuhi persyaratan umum, yang tidak dapat diekspos oleh kelas dasar virtual. Bahkan, seluruh implementasi paradigma orientasi objek di C ++ bahkan tidak ada ketika kode templat sedang diproses. Tidak ada objek (belum). Itu

Kelas menggambarkan batasan pada objek yang akan digunakan untuk memeriksa persyaratan untuk fungsi memanipulasi objek tersebut. Konsep menjelaskan batasan pada tipe (termasuk kelas) yang akan digunakan untuk memeriksa persyaratan kode generik untuk menghasilkan kode nyata dari tipe tersebut dan kombinasi kode generik.

Jadi, sekali lagi, ini adalah "pemeriksaan kewarasan" yang sama, tetapi di lapisan lain bahasa, yaitu templat. Templat adalah bahasa lengkap (turing lengkap) yang memungkinkan meta-pemrograman, tipe pemrograman bahkan sebelum mereka muncul dalam kode yang dikompilasi. Ini sedikit seperti skrip kompiler. Katakanlah Anda dapat membuat skrip, kelas hanyalah nilai yang dimanipulasi oleh skrip. Saat ini, tidak ada cara untuk memeriksa batasan pada nilai-nilai ini selain untuk menghentikan skrip dengan cara yang tidak jelas. Konsepnya hanya itu: berikan pengetikan pada nilai-nilai ini (yang dalam kode yang dihasilkan adalah tipe). Tidak yakin saya jelas ...

Perbedaan lain yang sangat penting antara kelas dasar virtual dan Konsep adalah bahwa yang pertama memaksa hubungan yang kuat antara jenis, membuat mereka "terikat oleh darah". Sementara metaprogramming template memungkinkan "duck typing", Konsep hanya memungkinkan untuk membuat persyaratan menjadi lebih jelas.

Klaim
sumber
4

Mendeklarasikan kelas adalah " objek pemrograman ".
Mendeklarasikan konsep adalah " kelas pemrograman ".

Tentu saja, karena selalu pemrograman , analogi-analogi tertentu dapat dilihat, tetapi dua hal itu termasuk fase berbeda dari proses abstraksi. Pada dasarnya "kelas" (dan semuanya ada di sekitarnya, seperti "antarmuka") memberi tahu kompiler bagaimana menyusun objek yang akan dijalankan oleh mesin pelaksana saat runtime. "Konsep" dimaksudkan untuk memberi tahu kompiler bagaimana "kelas" harus disusun agar "dapat dikompilasi" dalam konteks yang diberikan.

Tentu saja, secara teori dimungkinkan untuk mengulangi langkah-langkah ini berulang-ulang

  • benda
  • jenis objek ( kelas )
  • jenis (jenis objek) ( konsep )
  • jenis (jenis (jenis objek)) ( ??? )
  • .....

Saat ini, "konsep" telah dijatuhkan oleh spesifikasi C ++ 0x (karena mereka masih memerlukan beberapa pekerjaan, dan dipertahankan bukan kasus untuk menunda lagi) Gagasan konsep n saya tidak tahu-benar sekarang- jika bisa bermanfaat.

Emilio Garavaglia
sumber
Itu masuk akal secara konseptual; Saya mencoba untuk meresap dan berpikir jika ini menjawab pertanyaan. Terima kasih banyak.
Gui Prá
3

Jawaban sederhana untuk hampir semua pertanyaan Anda adalah, "Karena penyedot C ++ menyedot". Serius. Mereka dibangun di atas teknologi Unit Terjemahan C, yang secara efektif melarang banyak hal bermanfaat, dan implementasi templat yang ada sangat lambat. Konsep tidak dipotong untuk alasan konseptual apa pun - mereka dipotong karena tidak ada implementasi yang dapat diandalkan, ConceptGCC sangat lambat, dan menspesifikasikan konsep memakan waktu lama. Herb Sutter menyatakan bahwa dibutuhkan lebih banyak ruang untuk menentukan konsep yang digunakan dalam pustaka Standar daripada menentukan seluruh templat.

Bisa dibilang, antara SFINAE, decltypedan static_assert, mereka sebagian besar dapat diterapkan seperti sekarang.

DeadMG
sumber
2

Dalam pemahaman saya, Antarmuka dan Konsep memiliki tujuan yang sama di berbagai bagian bahasa C ++.

Seperti disebutkan dalam jawaban untuk pertanyaan asli: Implementasi Antarmuka ditentukan oleh implementor kelas pada waktu desain. Setelah sebuah kelas diterbitkan, ia hanya dapat mendukung antarmuka yang berasal dari waktu desain.

Dua Antarmuka yang berbeda dengan fungsi dan semantik anggota yang sama persis (yaitu konsep yang sama) masih akan menjadi dua Antarmuka yang berbeda. Jika Anda ingin mendukung semantik kedua Antarmuka, Anda mungkin harus menerapkan dukungan dua kali.

Itulah masalah yang ingin dipecahkan oleh Pemrograman Generik. Dalam C ++ Generic Programming, jenis yang diteruskan ke templat hanya perlu mendukung antarmuka (tidak menggunakan huruf besar, dalam arti "antarmuka pemrograman" dari suatu jenis) yang digunakan oleh templat. Dua Antarmuka berbeda dengan fungsi anggota yang sama akan cocok, dan Anda harus menulis kode hanya sekali. Selain itu, semua jenis (bahkan tanpa Antarmuka eksplisit) yang mendukung antarmuka yang sama akan berfungsi dengan baik.

Ini mengarah ke masalah kedua: bagaimana jika Anda memiliki dua jenis dengan antarmuka yang tumpang tindih tetapi semantik yang berbeda ? Pemrograman generik tidak akan dapat membedakan dan semuanya akan dikompilasi dengan baik, tetapi hasil run-time akan mengejutkan dan mungkin salah.

Di situlah Konsep masuk. Jika Anda terlalu menyederhanakan Anda dapat mempertimbangkan Konsep versi generik (templat) Antarmuka. Itu perlu diimplementasikan hanya sekali untuk diterapkan ke sejumlah besar tipe potensial yang dapat "berasal" dari Konsep. Sebuah Konsep adalah antarmuka semantik yang telah ditentukan dari tipe (kelas-) yang tidak terbatas pada tipe itu saja. Ini tidak seperti Antarmuka di mana dua jenis yang sangat berbeda (ke kompiler generik) masih dapat memiliki Konsep yang sama, tanpa harus menggunakan untuk membatasi Antarmuka kelas dasar, atau Antarmuka kelas dasar yang tidak memiliki semantik nyata yang dapat dilihat oleh kompiler mereka. sendiri tetapi hanya digunakan untuk jenis perbedaan.

Joris Timmermans
sumber
Tunggu ... Anda mengatakan konsep diterapkan sekali dan berlaku untuk sejumlah besar tipe potensial yang dapat "diturunkan" darinya. Itu berarti semua tipe potensial harus diturunkan darinya. Jika dua konsep dengan konten yang sama ada di dua pustaka yang berbeda, tanpa pemetaan otomatis, Anda memiliki masalah yang sama dengan Antarmuka. Pada saat yang sama, sebuah Antarmuka dapat menampilkan pemetaan otomatis. Masalahnya terlihat satu dan sama bagi saya.
Gui Prá
1
@ n2liquid - Konsep adalah campuran manfaat dan kelemahan yang berbeda antara Antarmuka dan Pemrograman Generik murni. Mereka bukan perbaikan ketat. Konsep tidak menghindari bagian "Antarmuka" yang telah ditentukan sebelumnya. Konsep DO menghindari situasi di mana tipe kelas mendukung semantik yang sama tetapi tidak dapat berasal dari Antarmuka yang sama (misalnya di mana antarmuka didefinisikan untuk subtipe ganda, sedangkan versi generik berlaku untuk semua jenis numerik).
Joris Timmermans