Pertimbangkan kodenya:
#include <stdio.h>
class Base {
public:
virtual void gogo(int a){
printf(" Base :: gogo (int) \n");
};
virtual void gogo(int* a){
printf(" Base :: gogo (int*) \n");
};
};
class Derived : public Base{
public:
virtual void gogo(int* a){
printf(" Derived :: gogo (int*) \n");
};
};
int main(){
Derived obj;
obj.gogo(7);
}
Mendapat kesalahan ini:
> g ++ -pedantic -Os test.cpp -o test test.cpp: Dalam fungsi `int main () ': test.cpp: 31: error: tidak ada fungsi yang cocok untuk panggilan ke `Derived :: gogo (int) ' test.cpp: 21: note: kandidat adalah: virtual void Derived :: gogo (int *) test.cpp: 33: 2: peringatan: tidak ada baris baru di akhir file > Kode keluar: 1
Di sini, fungsi kelas turunan adalah melampaui semua fungsi dengan nama yang sama (bukan tanda tangan) di kelas dasar. Entah bagaimana, perilaku C ++ ini tidak terlihat OK. Bukan polimorfik.
c++
polymorphism
overriding
Aman Aggarwal
sumber
sumber
obj.Base::gogo(7);
masih berfungsi dengan memanggil fungsi tersembunyi.Jawaban:
Menilai dari kata-kata pertanyaan Anda (Anda menggunakan kata "sembunyikan"), Anda sudah tahu apa yang sedang terjadi di sini. Fenomena itu disebut "nama bersembunyi". Untuk beberapa alasan, setiap kali seseorang mengajukan pertanyaan tentang mengapa penyembunyian nama terjadi, orang yang menjawab mengatakan bahwa ini disebut "penyembunyian nama" dan menjelaskan cara kerjanya (yang mungkin sudah Anda ketahui), atau menjelaskan cara menimpanya (yang Anda tidak pernah bertanya tentang), tetapi tampaknya tidak ada yang peduli untuk menjawab pertanyaan "mengapa" yang sebenarnya.
Keputusan, alasan di balik nama bersembunyi, yaitu mengapa itu sebenarnya dirancang ke dalam C ++, adalah untuk menghindari perilaku yang berlawanan dengan intuisi, tak terduga dan berpotensi berbahaya yang mungkin terjadi jika rangkaian fungsi kelebihan beban yang diwarisi dibiarkan bercampur dengan perangkat saat ini. kelebihan di kelas yang diberikan. Anda mungkin tahu bahwa dalam C ++ resolusi kelebihan bekerja dengan memilih fungsi terbaik dari sekumpulan kandidat. Ini dilakukan dengan mencocokkan jenis argumen dengan jenis parameter. Aturan-aturan yang cocok kadang-kadang bisa menjadi rumit, dan seringkali mengarah pada hasil yang mungkin dianggap tidak logis oleh pengguna yang tidak siap. Menambahkan fungsi baru ke satu set yang sudah ada sebelumnya mungkin menghasilkan perubahan yang agak drastis dalam hasil resolusi kelebihan beban.
Sebagai contoh, katakanlah kelas dasar
B
memiliki fungsi anggotafoo
yang mengambil parameter tipevoid *
, dan semua panggilan untukfoo(NULL)
diselesaikanB::foo(void *)
. Katakanlah tidak ada nama yang disembunyikan dan iniB::foo(void *)
terlihat di banyak kelas yang berbedaB
. Namun, katakanlah dalam beberapa keturunan [, remote tidak langsung]D
kelasB
fungsifoo(int)
didefinisikan. Sekarang, tanpa menyembunyikan namaD
memiliki keduanyafoo(void *)
danfoo(int)
terlihat serta berpartisipasi dalam resolusi kelebihan. Fungsi mana yang akanfoo(NULL)
diselesaikan oleh panggilan , jika dilakukan melalui objek bertipeD
? Mereka akan menyelesaikannyaD::foo(int)
, karenaint
merupakan kecocokan yang lebih baik untuk integral nol (yaituNULL
) daripada jenis pointer apa pun. Jadi, di seluruh hierarki panggilan untukfoo(NULL)
menyelesaikan ke satu fungsi, sementara diD
(dan di bawah) mereka tiba-tiba memutuskan untuk yang lain.Contoh lain diberikan dalam Desain dan Evolusi C ++ , halaman 77:
Tanpa aturan ini, status b akan diperbarui sebagian, yang mengarah ke pemotongan.
Perilaku ini dianggap tidak diinginkan ketika bahasa dirancang. Sebagai pendekatan yang lebih baik, diputuskan untuk mengikuti spesifikasi "name hiding", yang berarti bahwa setiap kelas dimulai dengan "clean sheet" sehubungan dengan setiap nama metode yang dinyatakannya. Untuk mengesampingkan perilaku ini, diperlukan tindakan eksplisit dari pengguna: awalnya merupakan deklarasi ulang dari metode yang diwarisi (saat ini tidak digunakan lagi), sekarang merupakan penggunaan eksplisit dari deklarasi penggunaan.
Seperti yang Anda amati dengan benar di posting asli Anda (saya merujuk pada komentar "Bukan polimorfik"), perilaku ini mungkin dilihat sebagai pelanggaran hubungan IS-A antara kelas-kelas. Ini benar, tetapi tampaknya saat itu diputuskan bahwa pada akhirnya nama persembunyian akan terbukti menjadi kejahatan yang lebih kecil.
sumber
nullptr
saya akan keberatan dengan contoh Anda dengan mengatakan "jika Anda ingin memanggilvoid*
versi, Anda harus menggunakan tipe pointer". Apakah ada contoh berbeda di mana ini bisa buruk?d->foo()
tidak akan memberi Anda "Is-aBase
", tetapistatic_cast<Base*>(d)->foo()
akan , termasuk pengiriman dinamis.Aturan resolusi nama mengatakan bahwa pencarian nama berhenti di ruang lingkup pertama di mana nama yang cocok ditemukan. Pada titik itu, aturan resolusi kelebihan muatan masuk untuk menemukan kecocokan terbaik dari fungsi yang tersedia.
Dalam kasus ini,
gogo(int*)
ditemukan (sendiri) dalam lingkup kelas Derived, dan karena tidak ada konversi standar dari int ke int *, pencarian gagal.Solusinya adalah membawa deklarasi Base melalui deklarasi menggunakan di kelas Derived:
... akan mengizinkan aturan pencarian nama untuk menemukan semua kandidat dan dengan demikian resolusi kelebihan akan dilanjutkan seperti yang Anda harapkan.
sumber
Ini adalah "By Design". Dalam C ++ resolusi kelebihan untuk jenis metode ini bekerja seperti berikut ini.
Karena Berasal tidak memiliki fungsi pencocokan bernama "gogo", resolusi kelebihan gagal.
sumber
Menyembunyikan nama masuk akal karena mencegah ambiguitas dalam resolusi nama.
Pertimbangkan kode ini:
Jika
Base::func(float)
tidak disembunyikanDerived::func(double)
di dalam Derived, kita akan memanggil fungsi kelas dasar saat memanggildobj.func(0.f)
, meskipun float dapat dipromosikan menjadi double.Referensi: http://bastian.rieck.ru/blog/posts/2016/name_hiding_cxx/
sumber