Polimorfisme dalam C ++

129

AFAIK:

C ++ menyediakan tiga jenis polimorfisme.

  • Fungsi virtual
  • Kelebihan nama fungsi
  • Kelebihan operator

Selain tiga jenis polimorfisme di atas, ada jenis polimorfisme lainnya:

  • run-time
  • waktu kompilasi
  • polimorfisme ad-hoc
  • polimorfisme parametrik

Saya tahu bahwa polimorfisme runtime dapat dicapai dengan fungsi virtual dan polimorfisme statis dapat dicapai dengan fungsi templat

Tapi untuk dua lainnya

polimorfisme ad-hoc:

Jika kisaran jenis aktual yang dapat digunakan terbatas dan kombinasi harus ditentukan secara individual sebelum digunakan, ini disebut polimorfisme ad-hoc.

polimorfisme parametrik:

Jika semua kode ditulis tanpa menyebutkan jenis tertentu dan dengan demikian dapat digunakan secara transparan dengan sejumlah jenis baru itu disebut parametrik polimorfisme.

Saya hampir tidak bisa memahaminya :(

adakah yang bisa menjelaskan keduanya jika memungkinkan dengan sebuah contoh? Saya berharap jawaban atas pertanyaan ini akan membantu banyak lulus baru dari perguruan tinggi mereka.

Vijay
sumber
30
Sebenarnya, C ++ memiliki empat jenis polimorfisme: parametrik (kemurahan hati melalui templat dalam C ++), inklusi (subtyping melalui metode virtual dalam C ++), kelebihan beban dan paksaan (konversi implisit). Secara konseptual, ada sedikit perbedaan antara overloading fungsi dan overloading operator.
fredoverflow
Jadi sepertinya situs yang saya sebutkan menyesatkan banyak..saya benar?
Vijay
@zombie: situs web itu menyentuh banyak konsep bagus, tetapi tidak tepat dan konsisten dalam penggunaan terminologinya (misalnya, setelah mulai berbicara tentang polimorfisme dispatch / runtime virtual, ia membuat banyak pernyataan tentang polimorfisme yang salah secara umum tetapi berlaku untuk pengiriman virtual). Jika Anda sudah memahami subjeknya, Anda dapat menghubungkan dengan apa yang dikatakan dan secara mental memasukkan peringatan yang diperlukan, tetapi sulit untuk sampai ke sana dengan membaca situs ....
Tony Delroy
Beberapa istilah hampir sinonim, atau lebih terkait tetapi lebih dibatasi daripada istilah lainnya. Sebagai contoh, istilah "polimorfisme ad-hoc" sebagian besar digunakan di Haskell dalam pengalaman saya, namun "fungsi virtual" sangat terkait erat. Perbedaan kecil adalah bahwa "fungsi virtual" adalah istilah berorientasi objek yang merujuk pada fungsi anggota dengan "pengikatan lambat". "Pengiriman ganda" juga semacam polimorfisme ad-hoc. Dan seperti yang dikatakan FredOverflow, operator dan fungsi kelebihan beban pada dasarnya adalah hal yang sama.
Steve314
Saya memperbaiki format Anda untuk Anda. Harap baca bantuan yang tersedia di sebelah kanan panel edit. Seseorang dengan> 200 pertanyaan dan> 3k harus mengetahui hal-hal dasar ini. Anda juga mungkin ingin membeli keyboard baru. Kunci shift yang satu ini tampaknya gagal sebentar-sebentar. Oh, dan: tidak ada yang namanya "fungsi template" di C ++. Namun, ada templat fungsi .
sbi

Jawaban:

219

Memahami / persyaratan untuk polimorfisme

Untuk memahami polimorfisme - seperti istilah yang digunakan dalam Ilmu Komputer - itu membantu untuk memulai dari tes sederhana untuk dan definisi itu. Mempertimbangkan:

    Type1 x;
    Type2 y;

    f(x);
    f(y);

Di sini, f()adalah untuk melakukan beberapa operasi dan diberi nilai xdan ysebagai input.

Untuk menunjukkan polimorfisme, f() harus dapat beroperasi dengan nilai-nilai dari setidaknya dua jenis yang berbeda (misalnya intdan double), menemukan dan mengeksekusi kode yang sesuai jenis yang berbeda.


Mekanisme C ++ untuk polimorfisme

Polimorfisme khusus yang ditentukan oleh programmer

Anda dapat menulis f()sedemikian rupa sehingga dapat beroperasi pada banyak jenis dengan salah satu cara berikut:

  • Preprocessing:

    #define f(X) ((X) += 2)
    // (note: in real code, use a longer uppercase name for a macro!)
  • Overloading:

    void f(int& x)    { x += 2; }
    
    void f(double& x) { x += 2; }
  • Templat:

    template <typename T>
    void f(T& x) { x += 2; }
  • Pengiriman virtual:

    struct Base { virtual Base& operator+=(int) = 0; };
    
    struct X : Base
    {
        X(int n) : n_(n) { }
        X& operator+=(int n) { n_ += n; return *this; }
        int n_;
    };
    
    struct Y : Base
    {
        Y(double n) : n_(n) { }
        Y& operator+=(int n) { n_ += n; return *this; }
        double n_;
    };
    
    void f(Base& x) { x += 2; } // run-time polymorphic dispatch

Mekanisme terkait lainnya

Polimorfisme yang disediakan oleh kompiler untuk tipe bawaan, Konversi standar, dan casting / paksaan dibahas nanti untuk kelengkapan sebagai:

  • mereka umumnya dimengerti secara intuisi (menjamin " oh, itu reaksi "),
  • mereka memengaruhi ambang batas dalam membutuhkan, dan kelancaran dalam menggunakan, mekanisme di atas, dan
  • Penjelasan adalah pengalih perhatian dari konsep yang lebih penting.

Terminologi

Kategorisasi lebih lanjut

Dengan adanya mekanisme polimorfik di atas, kita dapat mengategorikannya dengan berbagai cara:

  • Kapan kode khusus tipe polimorfik dipilih?

    • Run time berarti kompiler harus menghasilkan kode untuk semua jenis yang mungkin ditangani oleh program saat berjalan, dan pada saat run-time kode yang benar dipilih ( pengiriman virtual )
    • Waktu kompilasi berarti pilihan kode tipe-spesifik dibuat selama kompilasi. Konsekuensi dari ini: katakanlah sebuah program hanya disebut di fatas dengan intargumen - tergantung pada mekanisme polimorfik yang digunakan dan pilihan inlining yang mungkin dihindari oleh pembuat kompiler f(double), atau kode yang dihasilkan mungkin dibuang pada titik tertentu dalam kompilasi atau penautan. ( semua mekanisme di atas kecuali pengiriman virtual )

  • Jenis apa yang didukung?

    • Ad-hoc artinya Anda memberikan kode eksplisit untuk mendukung setiap jenis (mis. Kelebihan beban, spesialisasi templat); Anda secara eksplisit menambahkan dukungan "untuk ini" (sesuai arti ad hoc ), beberapa lainnya "ini", dan mungkin "itu" juga ;-).
    • Arti parametrik Anda hanya dapat mencoba menggunakan fungsi untuk berbagai jenis parameter tanpa secara khusus melakukan apa pun untuk mengaktifkan dukungannya bagi mereka (misalnya templat, makro). Objek dengan fungsi / operator yang bertindak seperti templat / makro mengharapkan 1 adalah semua yang dibutuhkan templat / makro untuk melakukan tugasnya, dengan tipe yang tepat tidak relevan. "Konsep" yang diperkenalkan oleh C ++ 20 mengekspresikan dan menegakkan harapan tersebut - lihat halaman cppreference di sini .

      • Polimorfisme parametrik memberikan ketikan bebek - sebuah konsep yang dikaitkan dengan James Whitcomb Riley yang tampaknya berkata "Ketika saya melihat seekor burung yang berjalan seperti bebek dan berenang seperti bebek dan dukun seperti bebek, saya menyebut burung itu bebek." .

        template <typename Duck>
        void do_ducky_stuff(const Duck& x) { x.walk().swim().quack(); }
        
        do_ducky_stuff(Vilified_Cygnet());
    • Subtype (alias inklusi) polimorfisme memungkinkan Anda untuk bekerja pada tipe baru tanpa memperbarui algoritma / fungsi, tetapi mereka harus diturunkan dari kelas dasar yang sama (pengiriman virtual)

1 - Template sangat fleksibel. SFINAE (lihat juga std::enable_if) secara efektif memungkinkan beberapa rangkaian harapan untuk polimorfisme parametrik. Misalnya, Anda dapat menyandikan bahwa ketika tipe data yang Anda proses memiliki .size()anggota Anda akan menggunakan satu fungsi, jika tidak, fungsi lain yang tidak perlu .size()(tetapi mungkin menderita dalam beberapa cara - misalnya menggunakan yang lebih lambat strlen()atau tidak mencetak sebagai berguna pesan dalam log). Anda juga dapat menentukan perilaku ad-hoc ketika template dibuat dengan parameter tertentu, baik meninggalkan beberapa parameter parametrik ( spesialisasi template parsial ) atau tidak ( spesialisasi penuh ).

"Polimorfik"

Alf Steinbach berkomentar bahwa dalam C ++ Standard polymorphic hanya mengacu pada run-time polymorphism menggunakan virtual dispatch. General Comp. Sci. artinya lebih inklusif, sesuai dengan C ++ glosarium pencipta Bjarne Stroustrup ( http://www.stroustrup.com/glossary.html ):

polymorphism - menyediakan antarmuka tunggal untuk entitas dari berbagai jenis. Fungsi virtual menyediakan polimorfisme dinamis (run-time) melalui antarmuka yang disediakan oleh kelas dasar. Fungsi dan template yang kelebihan beban menyediakan polimorfisme statis (waktu kompilasi). TC ++ PL 12.2.6, 13.6.1, A&P 2.9.

Jawaban ini - seperti pertanyaannya - menghubungkan fitur-fitur C ++ ke Comp. Sci. terminologi.

Diskusi

Dengan C ++ Standard menggunakan definisi sempit "polimorfisme" daripada Comp. Sci. komunitas, untuk memastikan saling pengertian bagi audiens Anda pertimbangkan ...

  • menggunakan terminologi yang tidak ambigu ("bisakah kita membuat kode ini dapat digunakan kembali untuk jenis lain?" atau "bisakah kita menggunakan pengiriman virtual?" daripada "bisakah kita membuat kode ini polimorfik?"), dan / atau
  • mendefinisikan terminologi Anda dengan jelas.

Namun, apa yang penting untuk menjadi programmer C ++ yang hebat adalah memahami apa yang sebenarnya dilakukan polimorfisme untuk Anda ...

    memungkinkan Anda menulis kode "algoritmik" satu kali dan kemudian menerapkannya ke banyak jenis data

... dan kemudian sangat sadar bagaimana mekanisme polimorfik yang berbeda sesuai dengan kebutuhan Anda yang sebenarnya.

Setelan polimorfisme run-time:

  • input diproses dengan metode pabrik dan dimuntahkan sebagai koleksi objek heterogen yang ditangani melalui Base*s,
  • implementasi yang dipilih pada saat runtime berdasarkan pada file konfigurasi, sakelar baris perintah, pengaturan UI dll.,
  • implementasi bervariasi pada saat runtime, seperti untuk pola mesin negara.

Ketika tidak ada driver yang jelas untuk polimorfisme run-time, opsi waktu kompilasi lebih disukai. Mempertimbangkan:

  • kompilasi-apa yang disebut aspek kelas templated lebih baik daripada antarmuka lemak gagal saat runtime
  • SFINAE
  • CRTP
  • optimisasi (banyak termasuk penghapusan inlining dan kode mati, loop membuka gulungan, array berbasis stack statis vs heap)
  • __FILE__,, __LINE__string concatenation literal dan kemampuan unik makro lainnya (yang tetap jahat ;-))
  • templates dan macro test penggunaan semantik didukung, tetapi jangan secara artifisial membatasi bagaimana dukungan itu disediakan (seperti pengiriman virtual cenderung dengan membutuhkan pencocokan fungsi anggota yang persis sama)

Mekanisme lain yang mendukung polimorfisme

Seperti yang dijanjikan, untuk kelengkapan beberapa topik tambahan dibahas:

  • overload yang disediakan kompiler
  • konversi
  • gips / paksaan

Jawaban ini diakhiri dengan diskusi tentang bagaimana kombinasi di atas untuk memberdayakan dan menyederhanakan kode polimorfik - terutama polimorfisme parametrik (template dan makro).

Mekanisme untuk pemetaan ke operasi tipe-spesifik

> Overload yang disediakan compiler implisit

Secara konsep, kompiler membebani banyak operator untuk tipe builtin. Secara konseptual tidak berbeda dari overload yang ditentukan pengguna, tetapi terdaftar karena mudah diabaikan. Misalnya, Anda dapat menambahkan ke intdan doublemenggunakan notasi yang sama x += 2dan kompiler menghasilkan:

  • instruksi CPU tipe-spesifik
  • hasil dari jenis yang sama.

Overloading kemudian meluas ke tipe yang ditentukan pengguna:

std::string x;
int y = 0;

x += 'c';
y += 'c';

Overload yang disediakan oleh kompiler untuk tipe dasar adalah umum dalam bahasa komputer level tinggi (3GL +), dan diskusi eksplisit tentang polimorfisme umumnya menyiratkan sesuatu yang lebih. (2GL - bahasa assembly - sering mengharuskan programmer untuk secara eksplisit menggunakan mnemonik yang berbeda untuk jenis yang berbeda.)

> Konversi standar

Bagian keempat C ++ Standard menjelaskan konversi Standar.

Poin pertama merangkum dengan baik (dari konsep lama - semoga masih secara substansial benar):

-1- Konversi standar adalah konversi implisit yang ditentukan untuk tipe bawaan. Klausa conv merinci set lengkap konversi tersebut. Urutan konversi standar adalah urutan konversi standar dalam urutan berikut:

  • Nol atau satu konversi dari rangkaian berikut: konversi lvalue-ke-rvalue, konversi array-ke-pointer, dan konversi fungsi-ke-pointer.

  • Nol atau satu konversi dari rangkaian berikut: promosi integral, promosi floating point, konversi integral, konversi floating point, konversi floating-integral, konversi pointer, konversi pointer ke anggota, dan konversi boolean.

  • Nol atau satu konversi kualifikasi.

[Catatan: urutan konversi standar bisa kosong, yaitu tidak boleh ada konversi. ] Urutan konversi standar akan diterapkan ke ekspresi jika perlu untuk mengubahnya ke jenis tujuan yang diperlukan.

Konversi ini memungkinkan kode seperti:

double a(double x) { return x + 2; }

a(3.14);
a(42);

Menerapkan tes sebelumnya:

Untuk menjadi polimorfik, [ a()] harus dapat beroperasi dengan nilai setidaknya dua jenis yang berbeda (misalnya intdan double), menemukan dan mengeksekusi kode yang sesuai jenis .

a()sendiri menjalankan kode khusus untuk doubledan karena itu bukan polimorfik.

Tapi, dalam panggilan kedua ke a()kompiler tahu untuk menghasilkan kode yang sesuai jenis untuk "promosi floating point" (Standar §4) untuk dikonversi 42menjadi 42.0. Kode tambahan itu ada dalam fungsi panggilan . Kami akan membahas pentingnya hal ini dalam kesimpulan.

> Paksaan, gips, konstruktor implisit

Mekanisme ini memungkinkan kelas yang ditentukan pengguna untuk menentukan perilaku yang mirip dengan konversi standar tipe yang dibangun. Mari kita lihat:

int a, b;

if (std::cin >> a >> b)
    f(a, b);

Di sini, objek std::cindievaluasi dalam konteks boolean, dengan bantuan operator konversi. Ini dapat dikelompokkan secara konseptual dengan "promosi integral" dkk dari Konversi standar dalam topik di atas.

Konstruktor implisit secara efektif melakukan hal yang sama, tetapi dikendalikan oleh tipe cast-to:

f(const std::string& x);
f("hello");  // invokes `std::string::string(const char*)`

Implikasi dari overload, konversi, dan paksaan yang disediakan oleh kompiler

Mempertimbangkan:

void f()
{
    typedef int Amount;
    Amount x = 13;
    x /= 2;
    std::cout << x * 1.1;
}

Jika kita ingin jumlah xdiperlakukan sebagai bilangan real selama pembagian (yaitu menjadi 6,5 daripada dibulatkan ke 6), kita hanya perlu mengubah ke typedef double Amount.

Itu bagus, tetapi tidak akan terlalu banyak pekerjaan untuk membuat kode secara eksplisit "ketik benar":

void f()                               void f()
{                                      {
    typedef int Amount;                    typedef double Amount;
    Amount x = 13;                         Amount x = 13.0;
    x /= 2;                                x /= 2.0;
    std::cout << double(x) * 1.1;          std::cout << x * 1.1;
}                                      }

Tetapi, pertimbangkan bahwa kami dapat mengubah versi pertama menjadi template:

template <typename Amount>
void f()
{
    Amount x = 13;
    x /= 2;
    std::cout << x * 1.1;
}

Ini karena "fitur kenyamanan" kecil yang dapat dengan mudah dipakai untuk intatau doublebekerja sebagaimana dimaksud. Tanpa fitur-fitur ini, kita akan membutuhkan gips yang eksplisit, ketik ciri-ciri dan / atau kelas kebijakan, beberapa kekacauan yang cenderung kesalahan seperti:

template <typename Amount, typename Policy>
void f()
{
    Amount x = Policy::thirteen;
    x /= static_cast<Amount>(2);
    std::cout << traits<Amount>::to_double(x) * 1.1;
}

Jadi, overloading yang disediakan operator compiler untuk tipe builtin, Konversi standar, casting / koersi / konstruktor implisit - mereka semua memberikan dukungan halus untuk polimorfisme. Dari definisi di bagian atas jawaban ini, mereka membahas "menemukan dan mengeksekusi kode yang sesuai jenis" dengan memetakan:

  • "jauh" dari tipe parameter

    • dari banyak tipe data menangani kode algoritmik algoritmik polimorfik

    • untuk kode yang ditulis untuk sejumlah (berpotensi lebih rendah) dari jenis (sama atau lainnya).

  • "ke" tipe parametrik dari nilai tipe konstan

Mereka tidak membangun konteks polimorfik sendiri, tetapi membantu memberdayakan / menyederhanakan kode di dalam konteks tersebut.

Anda mungkin merasa ditipu ... sepertinya tidak banyak. Signifikansi adalah bahwa dalam konteks polimorfik parametrik (yaitu di dalam template atau makro), kami mencoba untuk mendukung berbagai jenis sewenang-wenang tetapi sering ingin mengekspresikan operasi pada mereka dalam hal fungsi lain, literal dan operasi yang dirancang untuk suatu set kecil jenis. Ini mengurangi kebutuhan untuk membuat fungsi atau data yang hampir identik pada basis per jenis ketika operasi / nilai secara logis sama. Fitur-fitur ini bekerja sama untuk menambah sikap "upaya terbaik", melakukan apa yang secara intuitif diharapkan dengan menggunakan fungsi dan data yang tersedia terbatas dan hanya berhenti dengan kesalahan ketika ada ambiguitas nyata.

Ini membantu membatasi kebutuhan kode polimorfik yang mendukung kode polimorfik, menarik jaring yang lebih ketat di sekitar penggunaan polimorfisme sehingga penggunaan yang terlokalisasi tidak memaksa penggunaan yang luas, dan membuat manfaat polimorfisme tersedia sesuai kebutuhan tanpa membebankan biaya karena harus mengekspos implementasi di mengkompilasi waktu, memiliki banyak salinan dari fungsi logis yang sama dalam kode objek untuk mendukung jenis yang digunakan, dan dalam melakukan pengiriman virtual sebagai lawan inlining atau setidaknya kompilasi waktu penyelesaian panggilan. Seperti biasa dalam C ++, programmer diberi banyak kebebasan untuk mengontrol batas-batas di mana polimorfisme digunakan.

Tony Delroy
sumber
1
-1 Jawaban yang bagus kecuali untuk pembahasan terminologi. Standar C ++ mendefinisikan istilah "polimorfik" dalam § 1.8 / 1, di sana mengacu pada bagian 10.3 tentang fungsi virtual. Jadi tidak ada ruang gerak, tidak ada ruang untuk diskusi, tidak ada ruang untuk pendapat pribadi: dalam konteks standar C ++ istilah itu didefinisikan sekali dan untuk semua. Dan itu memainkan peran dalam praktik. Misalnya, §5.2.7 / 6 tentang dynamic_castmemerlukan "penunjuk ke atau nilai dari tipe polimorfik". Ceria & hth.,
Ceria dan hth. - Alf
@Alf: referensi bagus - meskipun saya pikir perspektif Anda terlalu sempit. Sangat jelas dari daftar pertanyaan overloading, polimorfisme ad-hoc dan parametrik dll. Bahwa jawabannya harus menghubungkan kemampuan C ++ dengan Comp umum. Sci. arti dari istilah tersebut. Memang, glosarium Stroustrup mengatakan "polimorfisme - menyediakan antarmuka tunggal untuk entitas dari tipe yang berbeda. Fungsi virtual memberikan polimorfisme dinamis (run-time) melalui antarmuka yang disediakan oleh kelas dasar. Fungsi dan templat berlebih menyediakan polimorfisme statis (waktu kompilasi). TC ++ PL 12.2.6, 13.6.1, A&P 2.9. "
Tony Delroy
@ Tony: itu bukan dorongan utama dari jawaban Anda yang salah. tidak apa-apa, itu bagus. hanya saja wrt itu. terminologi Anda mendapatkannya mundur: terminologi akademik formal adalah yang sempit yang didefinisikan oleh Standar Internasional Kudus, dan terminologi kasar informal di mana orang dapat berarti hal-hal yang sedikit berbeda, adalah yang terutama digunakan dalam pertanyaan dan jawaban ini. Ceria & hth.,
Ceria dan hth. - Alf
@Alf: Saya berharap jawabannya hebat - "Mekanisme lain" perlu ditulis ulang dalam seperlima dari baris, dan saya merenungkan / menyusun fitur yang lebih konkret dan implikasi yang kontras dengan mekanisme polimorfik. Pokoknya, pemahaman saya adalah bahwa akademis formal eksklusif-C + + - makna terfokus mungkin sempit, tetapi Comp akademik umum formal. Sci. artinya tidak, sebagaimana dibuktikan oleh glosarium Stroustrup. Kami membutuhkan sesuatu yang pasti - misalnya definisi dari Knuth - belum beruntung googling. Saya menghargai Anda seorang guru C ++, tetapi dapatkah Anda menunjukkan bukti yang relevan tentang hal ini secara khusus?
Tony Delroy
1
@Alf: kedua, saya yakin polimorfisme secara formal didefinisikan dalam Comp General yang layak. Sci. buku dengan cara (abadi, stabil) yang kompatibel dengan penggunaan saya (dan Stroustrup). Artikel Wikipedia menautkan beberapa publikasi akademis yang mendefinisikannya seperti itu: "Fungsi polimorfik adalah fungsi yang operandnya (parameter aktual) dapat memiliki lebih dari satu jenis. Jenis polimorfik adalah jenis yang operasinya berlaku untuk nilai lebih dari satu jenis." (dari lucacardelli.name/Papers/OnUnderstanding.A4.pdf ). Jadi, pertanyaannya adalah "siapa yang berbicara untuk Comp. Sci" ...?
Tony Delroy
15

Dalam C ++, perbedaan penting adalah run-time vs binding-waktu kompilasi. Ad-hoc vs parametrik tidak terlalu membantu, seperti yang akan saya jelaskan nanti.

|----------------------+--------------|
| Form                 | Resolved at  |
|----------------------+--------------|
| function overloading | compile-time |
| operator overloading | compile-time |
| templates            | compile-time |
| virtual methods      | run-time     |
|----------------------+--------------|

Catatan - polimorfisme run-time mungkin masih dapat diselesaikan pada waktu kompilasi, tapi itu hanya optimasi. Perlu untuk mendukung resolusi run-time secara efisien, dan berdagang melawan masalah lain, adalah bagian dari apa yang menyebabkan fungsi virtual menjadi seperti itu. Dan itu benar-benar penting untuk semua bentuk polimorfisme dalam C ++ - masing-masing muncul dari rangkaian pengorbanan berbeda yang dibuat dalam konteks yang berbeda.

Kelebihan fungsi dan kelebihan operator adalah hal yang sama dalam setiap hal yang penting. Nama dan sintaksis untuk menggunakannya tidak memengaruhi polimorfisme.

Templat memungkinkan Anda menentukan banyak fungsi berlebih sekaligus.

Ada serangkaian nama lain untuk ide resolusi-waktu yang sama ...

|---------------+--------------|
| early binding | compile-time |
| late binding  | run-time     |
|---------------+--------------|

Nama-nama ini lebih terkait dengan OOP, jadi agak aneh untuk mengatakan bahwa templat atau fungsi non-anggota lainnya menggunakan penjilidan awal.

Untuk lebih memahami hubungan antara fungsi virtual dan fungsi yang berlebihan, penting juga untuk memahami perbedaan antara "pengiriman tunggal" dan "pengiriman ganda". Idenya dapat dipahami sebagai sebuah perkembangan ...

  • Pertama, ada fungsi monomorfik. Implementasi fungsi diidentifikasi secara unik oleh nama fungsi. Tidak ada parameter yang spesial.
  • Lalu, ada pengiriman tunggal. Salah satu parameter dianggap spesial, dan digunakan (beserta namanya) untuk mengidentifikasi implementasi yang akan digunakan. Dalam OOP, kita cenderung menganggap parameter ini sebagai "objek", daftar sebelum nama fungsi dll.
  • Lalu, ada beberapa pengiriman. Setiap / semua parameter berkontribusi untuk mengidentifikasi implementasi mana yang akan digunakan. Karena itu, sekali lagi, tidak ada parameter yang perlu khusus.

Jelas ada lebih banyak OOP daripada alasan untuk menominasikan satu parameter sebagai istimewa, tetapi itu adalah salah satu bagian darinya. Dan berkaitan dengan apa yang saya katakan tentang pertukaran - pengiriman tunggal cukup mudah dilakukan secara efisien (implementasi yang biasa disebut "tabel virtual"). Pengiriman ganda lebih canggung, tidak hanya dalam hal efisiensi, tetapi juga untuk kompilasi terpisah. Jika Anda penasaran, Anda mungkin mencari "masalah ekspresi".

Sama seperti itu agak aneh untuk menggunakan istilah "pengikatan awal" untuk fungsi non-anggota, itu agak aneh untuk menggunakan istilah "pengiriman tunggal" dan "pengiriman ganda" di mana polimorfisme diselesaikan pada waktu kompilasi. Biasanya, C ++ dianggap tidak memiliki banyak pengiriman, yang dianggap sebagai jenis resolusi run-time tertentu. Namun, fungsi overloading dapat dilihat sebagai pengiriman ganda yang dilakukan pada waktu kompilasi.

Kembali ke polimorfisme parametrik vs ad-hoc, istilah-istilah ini lebih populer dalam pemrograman fungsional, dan mereka tidak cukup berfungsi dalam C ++. Walaupun demikian...

Polimorfisme parametrik berarti bahwa Anda memiliki tipe sebagai parameter, dan kode yang sama persis digunakan terlepas dari tipe apa yang Anda gunakan untuk parameter tersebut.

Polimorfisme ad-hoc adalah ad-hoc dalam arti bahwa Anda memberikan kode yang berbeda tergantung pada jenis tertentu.

Overloading dan fungsi virtual adalah contoh dari polimorfisme ad-hoc.

Sekali lagi, ada beberapa sinonim ...

|------------+---------------|
| parametric | unconstrained |
| ad-hoc     | constrained   |
|------------+---------------|

Kecuali ini bukan sinonim, meskipun mereka umumnya diperlakukan seolah-olah mereka, dan di situlah kebingungan cenderung muncul di C ++.

Alasan di balik memperlakukan ini sebagai sinonim adalah bahwa dengan membatasi polimorfisme untuk kelas tipe tertentu, menjadi mungkin untuk menggunakan operasi khusus untuk kelas tipe tersebut. Kata "kelas" di sini dapat diartikan dalam arti OOP, tetapi sebenarnya hanya merujuk pada (biasanya disebut) set tipe yang berbagi operasi tertentu.

Jadi polimorfisme parametrik biasanya diambil (setidaknya secara default) untuk menyiratkan polimorfisme yang tidak dibatasi. Karena kode yang sama digunakan terlepas dari parameter tipe, satu-satunya operasi yang didukung adalah yang bekerja untuk semua tipe. Dengan membiarkan rangkaian tipe tidak dibatasi, Anda sangat membatasi rangkaian operasi yang dapat Anda terapkan pada tipe tersebut.

Dalam misalnya Haskell, Anda dapat memiliki ...

myfunc1 :: Bool -> a -> a -> a
myfunc1 c x y = if c then x else y

Di asini adalah tipe polimorfik yang tidak dibatasi. Itu bisa apa saja, jadi tidak banyak yang bisa kita lakukan dengan nilai-nilai dari tipe itu.

myfunc2 :: Num a => a -> a
myfunc2 x = x + 3

Di sini, adibatasi untuk menjadi anggota Numkelas - tipe yang bertindak seperti angka. Batasan itu memungkinkan Anda untuk melakukan hal-hal nomor dengan nilai-nilai tersebut, seperti menambahkannya. Bahkan 3inferensi tipe polimorfik menunjukkan bahwa yang Anda maksud adalah 3tipe a.

Saya menganggap ini sebagai polimorfisme parametrik terbatas. Hanya ada satu implementasi, tetapi hanya bisa diterapkan dalam kasus terbatas. Aspek ad-hoc adalah pilihan yang digunakan +dan 3untuk digunakan. Setiap "instance" dari Nummemiliki implementasi yang berbeda dari ini. Jadi, bahkan dalam Haskell "parametrik" dan "tidak dibatasi" tidak benar-benar sinonim - jangan salahkan saya, itu bukan salah saya!

Dalam C ++, baik fungsi overloading dan virtual adalah polimorfisme ad-hoc. Definisi polimorfisme ad-hoc tidak peduli apakah implementasinya dipilih saat run-time atau compile-time.

C ++ menjadi sangat dekat dengan polimorfisme parametrik dengan templat jika setiap parameter templat memiliki tipe typename. Ada tipe parameter, dan ada implementasi tunggal tidak peduli tipe apa yang digunakan. Namun, aturan "Kegagalan Substitusi Bukan Kesalahan" berarti bahwa kendala implisit muncul sebagai akibat dari penggunaan operasi dalam templat. Komplikasi tambahan termasuk spesialisasi templat untuk menyediakan templat alternatif - implementasi berbeda (ad-hoc).

Jadi dengan cara C ++ memiliki polimorfisme parametrik, tetapi secara implisit dibatasi dan dapat ditimpa oleh alternatif ad-hoc - yaitu klasifikasi ini tidak benar-benar berfungsi untuk C ++.

Steve314
sumber
+1 Banyak poin dan wawasan menarik. Saya hanya menghabiskan beberapa jam membaca tentang Haskell sehingga " aini adalah tipe polimorfik yang tidak dibatasi [...] sehingga tidak banyak yang bisa kita lakukan dengan nilai-nilai dari tipe itu." sangat menarik - dalam C ++ sans Konsep Anda tidak terbatas hanya mencoba serangkaian operasi tertentu pada argumen dari jenis yang ditentukan sebagai parameter templat ... perpustakaan seperti meningkatkan konsep bekerja dengan cara lain - memastikan jenis mendukung operasi Anda menentukan, alih-alih menjaga agar operasi tambahan tidak disengaja.
Tony Delroy
@Tony - Konsep adalah cara untuk secara eksplisit membatasi polimorfisme template. Kendala implisit jelas tidak akan hilang karena kompatibilitas, tetapi kendala eksplisit pasti akan meningkatkan hal-hal secara signifikan. Saya cukup yakin bahwa beberapa rencana masa lalu untuk konsep agak terkait dengan kacamata jenis Haskell, meskipun saya tidak melihat ke dalamnya terlalu dalam dan ketika saya terakhir kali melihat "dangkal" saya tidak tahu banyak tentang Haskell.
Steve314
"Kendala implisit jelas tidak akan hilang karena kompatibilitas" - dari memori, C ++ 0x Konsep memang (berjanji untuk: - /) mencegah "kendala implisit" - Anda hanya bisa menggunakan jenis dengan cara yang dijanjikan oleh Konsep.
Tony Delroy
2

Adapun polimorfisme ad-hoc, itu berarti overloading fungsi atau overloading operator. Lihat di sini:

http://en.wikipedia.org/wiki/Ad-hoc_polymorphism

Adapun polimorfisme parametrik, fungsi template juga dapat dihitung karena mereka tidak selalu mengambil parameter tipe TETAP. Misalnya, satu fungsi dapat mengurutkan array bilangan bulat dan juga dapat mengurutkan array string, dll.

http://en.wikipedia.org/wiki/Parametric_polymorphism

Eric Z
sumber
1
Sayangnya, meskipun benar, ini menyesatkan. Fungsi template dapat memperoleh kendala implisit karena aturan SFINAE - menggunakan operasi di dalam template yang secara implisit membatasi polimorfisme - dan spesialisasi template dapat menyediakan template alternatif ad-hoc yang menggantikan template yang lebih umum. Jadi templat (secara default) menyediakan polimorfisme parametrik yang tidak dibatasi, tetapi tidak ada penegakannya - setidaknya ada dua cara yang dapat menjadi dibatasi atau ad-hoc.
Steve314
Bahkan contoh Anda - pengurutan - menyiratkan kendala. Penyortiran hanya berfungsi untuk jenis yang dipesan (mis. Sediakan <operator dan sejenisnya). Di Haskell, Anda akan mengungkapkan persyaratan itu secara eksplisit menggunakan kelas Ord. Fakta bahwa Anda mendapatkan perbedaan <tergantung pada jenis tertentu (seperti yang disediakan oleh contoh Ord) akan dianggap sebagai polimorfisme ad-hoc.
Steve314
2

Ini mungkin tidak membantu, tetapi saya membuat ini untuk memperkenalkan teman-teman saya ke pemrograman dengan memberikan fungsi yang didefinisikan, seperti START, dan ENDuntuk fungsi utama sehingga tidak terlalu menakutkan (mereka hanya menggunakan file main.cpp ). Ini berisi kelas dan struct Polymorphic, templates, vektor, array, arahan preproccessor, persahabatan, operator dan pointer (yang semuanya mungkin harus Anda ketahui sebelum mencoba polimorfisme):

Catatan: Belum selesai, tetapi Anda bisa mendapatkan ide

main.cpp

#include "main.h"
#define ON_ERROR_CLEAR_SCREEN false
START
    Library MyLibrary;
    Book MyBook("My Book", "Me");
    MyBook.Summarize();
    MyBook += "Hello World";
    MyBook += "HI";
    MyBook.EditAuthor("Joe");
    MyBook.EditName("Hello Book");
    MyBook.Summarize();
    FixedBookCollection<FairyTale> FBooks("Fairytale Books");
    FairyTale MyTale("Tale", "Joe");
    FBooks += MyTale;
    BookCollection E("E");
    MyLibrary += E;
    MyLibrary += FBooks;
    MyLibrary.Summarize();
    MyLibrary -= FBooks;
    MyLibrary.Summarize();
    FixedSizeBookCollection<5> Collection("My Fixed Size Collection");
    /* Extension Work */ Book* Duplicate = MyLibrary.DuplicateBook(&MyBook);
    /* Extension Work */ Duplicate->Summarize();
END

main.h

#include <iostream>
#include <sstream>
#include <vector>
#include <string>
#include <type_traits>
#include <array>
#ifndef __cplusplus
#error Not C++
#endif
#define START int main(void)try{
#define END GET_ENTER_EXIT return(0);}catch(const std::exception& e){if(ON_ERROR_CLEAR_SCREEN){system("cls");}std::cerr << "Error: " << e.what() << std::endl; GET_ENTER_EXIT return (1);}
#define GET_ENTER_EXIT std::cout << "Press enter to exit" << std::endl; getchar();
class Book;
class Library;
typedef std::vector<const Book*> Books;
bool sContains(const std::string s, const char c){
    return (s.find(c) != std::string::npos);
}
bool approve(std::string s){
    return (!sContains(s, '#') && !sContains(s, '%') && !sContains(s, '~'));
}
template <class C> bool isBook(){
    return (typeid(C) == typeid(Book) || std::is_base_of<Book, C>());
}
template<class ClassToDuplicate> class DuplicatableClass{ 
public:
    ClassToDuplicate* Duplicate(ClassToDuplicate ToDuplicate){
        return new ClassToDuplicate(ToDuplicate);
    }
};
class Book : private DuplicatableClass<Book>{
friend class Library;
friend struct BookCollection;
public:
    Book(const char* Name, const char* Author) : name_(Name), author_(Author){}
    void operator+=(const char* Page){
        pages_.push_back(Page);
    }
    void EditAuthor(const char* AuthorName){
        if(approve(AuthorName)){
            author_ = AuthorName;
        }
        else{
            std::ostringstream errorMessage;
            errorMessage << "The author of the book " << name_ << " could not be changed as it was not approved";
            throw std::exception(errorMessage.str().c_str());
        }
    }
    void EditName(const char* Name){
        if(approve(Name)){
            name_ = Name;
        }
        else{
            std::ostringstream errorMessage;
            errorMessage << "The name of the book " << name_ << " could not be changed as it was not approved";
            throw std::exception(errorMessage.str().c_str());
        }
    }
    virtual void Summarize(){
        std::cout << "Book called " << name_ << "; written by " << author_ << ". Contains "
            << pages_.size() << ((pages_.size() == 1) ? " page:" : ((pages_.size() > 0) ? " pages:" : " pages")) << std::endl;
        if(pages_.size() > 0){
            ListPages(std::cout);
        }
    }
private:
    std::vector<const char*> pages_;
    const char* name_;
    const char* author_;
    void ListPages(std::ostream& output){
        for(int i = 0; i < pages_.size(); ++i){
            output << pages_[i] << std::endl;
        }
    }
};
class FairyTale : public Book{
public:
    FairyTale(const char* Name, const char* Author) : Book(Name, Author){}
};
struct BookCollection{
friend class Library;
    BookCollection(const char* Name) : name_(Name){}
    virtual void operator+=(const Book& Book)try{
        Collection.push_back(&Book); 
    }catch(const std::exception& e){
        std::ostringstream errorMessage;
        errorMessage << e.what() << " - on line (approx.) " << (__LINE__ -3);
        throw std::exception(errorMessage.str().c_str());
    }
    virtual void operator-=(const Book& Book){
        for(int i = 0; i < Collection.size(); ++i){
            if(Collection[i] == &Book){
                Collection.erase(Collection.begin() + i);
                return;
            }
        }
        std::ostringstream errorMessage;
        errorMessage << "The Book " << Book.name_ << " was not found, and therefore cannot be erased";
        throw std::exception(errorMessage.str().c_str());
    }
private:
    const char* name_;
    Books Collection;
};
template<class FixedType> struct FixedBookCollection : public BookCollection{
    FixedBookCollection(const char* Name) : BookCollection(Name){
        if(!isBook<FixedType>()){
            std::ostringstream errorMessage;
            errorMessage << "The type " << typeid(FixedType).name() << " cannot be initialized as a FixedBookCollection";
            throw std::exception(errorMessage.str().c_str());
            delete this;
        }
    }
    void operator+=(const FixedType& Book)try{
        Collection.push_back(&Book); 
    }catch(const std::exception& e){
        std::ostringstream errorMessage;
        errorMessage << e.what() << " - on line (approx.) " << (__LINE__ -3);
        throw std::exception(errorMessage.str().c_str());
    }
    void operator-=(const FixedType& Book){
        for(int i = 0; i < Collection.size(); ++i){
            if(Collection[i] == &Book){
                Collection.erase(Collection.begin() + i);
                return;
            }
        }
        std::ostringstream errorMessage;
        errorMessage << "The Book " << Book.name_ << " was not found, and therefore cannot be erased";
        throw std::exception(errorMessage.str().c_str());
    }
private:
    std::vector<const FixedType*> Collection;
};
template<size_t Size> struct FixedSizeBookCollection : private std::array<const Book*, Size>{
    FixedSizeBookCollection(const char* Name) : name_(Name){ if(Size < 1){ throw std::exception("A fixed size book collection cannot be smaller than 1"); currentPos = 0; } }
    void operator+=(const Book& Book)try{
        if(currentPos + 1 > Size){
            std::ostringstream errorMessage;
            errorMessage << "The FixedSizeBookCollection " << name_ << "'s size capacity has been overfilled";
            throw std::exception(errorMessage.str().c_str());
        }
        this->at(currentPos++) = &Book;
    }catch(const std::exception& e){
        std::ostringstream errorMessage;
        errorMessage << e.what() << " - on line (approx.) " << (__LINE__ -3);
        throw std::exception(errorMessage.str().c_str());
    }
private:
    const char* name_;
    int currentPos;
};
class Library : private std::vector<const BookCollection*>{
public:
    void operator+=(const BookCollection& Collection){
        for(int i = 0; i < size(); ++i){
            if((*this)[i] == &Collection){
                std::ostringstream errorMessage;
                errorMessage << "The BookCollection " << Collection.name_ << " was already in the library, and therefore cannot be added";
                throw std::exception(errorMessage.str().c_str());
            }
        }
        push_back(&Collection);
    }
    void operator-=(const BookCollection& Collection){
        for(int i = 0; i < size(); ++i){
            if((*this)[i] == &Collection){
                erase(begin() + i);
                return;
            }
        }
        std::ostringstream errorMessage;
        errorMessage << "The BookCollection " << Collection.name_ << " was not found, and therefore cannot be erased";
        throw std::exception(errorMessage.str().c_str());
    }
    Book* DuplicateBook(Book* Book)const{
        return (Book->Duplicate(*Book));
    }
    void Summarize(){
        std::cout << "Library, containing " << size() << ((size() == 1) ? " book collection:" : ((size() > 0) ? " book collections:" : " book collections")) << std::endl;
        if(size() > 0){
            for(int i = 0; i < size(); ++i){
                std::cout << (*this)[i]->name_ << std::endl;
            }
        }
    }
};
Joe
sumber
1

Berikut adalah contoh dasar menggunakan kelas Polimorfik

#include <iostream>

class Animal{
public:
   Animal(const char* Name) : name_(Name){/* Add any method you would like to perform here*/
    virtual void Speak(){
        std::cout << "I am an animal called " << name_ << std::endl;
    }
    const char* name_;
};

class Dog : public Animal{
public:
    Dog(const char* Name) : Animal(Name) {/*...*/}
    void Speak(){
        std::cout << "I am a dog called " << name_ << std::endl;
    }
};

int main(void){
    Animal Bob("Bob");
    Dog Steve("Steve");
    Bob.Speak();
    Steve.Speak();
    //return (0);
}
pengguna2976089
sumber
0

Polimorfisme berarti banyak bentuk seperti itu digunakan untuk operator untuk bertindak berbeda dalam berbagai kasus. Polimorfisme digunakan untuk mengimplementasikan warisan. Sebagai contoh, kita telah mendefinisikan draw fn () untuk bentuk kelas maka draw fn dapat diimplementasikan untuk menggambar lingkaran, kotak, segitiga dan bentuk lainnya. (yang merupakan objek dari bentuk kelas)

Jayraj Srikriti Naidu
sumber
-3

Jika ada yang mengatakan CUT kepada orang-orang ini

The Surgeon
The Hair Stylist
The Actor

Apa yang akan terjadi?

The Surgeon would begin to make an incision.
The Hair Stylist would begin to cut someone's hair.
The Actor would abruptly stop acting out of the current scene, awaiting directorial guidance.

Jadi representasi di atas menunjukkan Apa itu polimorfisme (nama yang sama, perilaku yang berbeda) dalam OOP.

Jika Anda pergi untuk wawancara dan pewawancara meminta Anda memberi tahu / menunjukkan contoh langsung untuk polimorfisme di ruangan yang sama dengan tempat kami duduk, katakan-

Jawab - Pintu / Windows

Bingung Bagaimana?

Melalui Pintu / Jendela - seseorang bisa datang, udara bisa datang, cahaya bisa datang, hujan bisa datang, dll.

yaitu Satu bentuk perilaku yang berbeda (Polimorfisme).

Untuk memahaminya dengan lebih baik dan secara sederhana saya menggunakan contoh di atas .. Jika Anda memerlukan referensi untuk kode ikuti jawaban di atas.

Sanchit
sumber
Seperti yang saya sebutkan untuk pemahaman yang lebih baik tentang Polimorfisme dalam c ++ saya menggunakan contoh di atas. Ini mungkin membantu orang yang lebih baru untuk benar-benar memahami dan menghubungkan apa arti atau apa yang terjadi di balik kode saat melakukan wawancara. Terima kasih!
Sanchit
op bertanya "polimorfisme dalam c ++". jawaban Anda terlalu abstrak.
StahlRat