Fungsi dengan nama yang sama tetapi tanda tangan berbeda di kelas turunan

92

Saya memiliki fungsi dengan nama yang sama, tetapi dengan tanda tangan yang berbeda di kelas dasar dan turunan. Ketika saya mencoba menggunakan fungsi kelas dasar di kelas lain yang mewarisi dari turunan, saya menerima pesan kesalahan. Lihat kode berikut:

class A
{
    public:
    void foo(string s){};
};

class B : public A
{
    public:
    int foo(int i){};
};

class C : public B
{
    public:
    void bar()
    {
        string s;
        foo(s);
    }
};

Saya menerima kesalahan berikut dari kompiler gcc:

In member function `void C::bar()': no matching function for call to `C::foo(std::string&)' candidates are: int B::foo(int)

Jika saya menghapus int foo(int i){};dari kelas B, atau jika saya mengganti namanya foo1, semuanya berfungsi dengan baik.

Apa masalahnya dengan ini?

Igor Oks
sumber
1
Secara teknis merupakan duplikat dari pertanyaan ini, tetapi pertanyaan ini memiliki judul dan jawaban yang lebih baik.
Troubadour

Jawaban:

79

Fungsi di kelas turunan yang tidak menggantikan fungsi di kelas dasar tetapi memiliki nama yang sama akan menyembunyikan fungsi lain dengan nama yang sama di kelas dasar.

Secara umum dianggap praktik yang buruk untuk memiliki fungsi di kelas turunan yang memiliki nama yang sama dengan fungsi di kelas bass yang tidak dimaksudkan untuk menimpa fungsi kelas dasar karena yang Anda lihat biasanya bukan perilaku yang diinginkan. Biasanya lebih baik memberikan nama yang berbeda untuk fungsi yang berbeda.

Jika Anda perlu memanggil fungsi dasar, Anda perlu mencakup panggilan dengan menggunakan A::foo(s). Perhatikan bahwa ini juga akan menonaktifkan mekanisme fungsi virtual apa pun A::foo(string)pada waktu yang sama.

CB Bailey
sumber
13
juga membaca jawaban litdb: Anda dapat 'memperlihatkan' fungsi dasar dengan klausa 'menggunakan A :: foo' di B.
xtofl
Benar, saya baru saja melihat solusi yang dapat digunakan di situs panggilan, memperlakukan hierarki dasar sebagai tetap.
CB Bailey
2
Apa dasar dari klaim ini dan diikuti dengan saran: "Secara umum dianggap praktik yang buruk untuk memiliki fungsi di kelas turunan yang memiliki nama yang sama dengan fungsi di kelas bass yang tidak dimaksudkan untuk menggantikan fungsi kelas dasar seperti apa yang Anda lihat biasanya bukan perilaku yang diinginkan. Biasanya lebih baik memberikan nama yang berbeda untuk fungsi yang berbeda " . Bagaimana jika mereka melakukan hal yang sama secara semantik? C ++ memberi Anda solusi untuk masalah yang disebabkan oleh hal ini, seperti yang dijelaskan oleh jawaban Johannes.
Nawaz
109

Itu karena pencarian nama berhenti jika menemukan nama di salah satu basis Anda. Itu tidak akan terlihat di pangkalan lain. Fungsi di B membayangi fungsi di A. Anda harus mendeklarasikan kembali fungsi A dalam lingkup B, sehingga kedua fungsi tersebut terlihat dari dalam B dan C:

class A
{
    public:
    void foo(string s){};
};

class B : public A
{
    public:
    int foo(int i){};
    using A::foo;
};

class C : public B
{
    public:
    void bar()
    {
        string s;
        foo(s);
    }
};

Sunting: Deskripsi sebenarnya yang diberikan Standar adalah (dari 10.2 / 2):

Langkah-langkah berikut menentukan hasil pencarian nama dalam ruang lingkup kelas, C. Pertama, setiap deklarasi untuk nama di kelas dan di setiap sub-objek kelas dasarnya dipertimbangkan. Sebuah nama anggota f dalam satu sub-objek B menyembunyikan nama anggota f dalam sub-objek A jika A adalah sub-objek kelas dasar B. Setiap deklarasi yang begitu tersembunyi dieliminasi dari pertimbangan. Masing-masing deklarasi ini yang diperkenalkan oleh deklarasi use dianggap dari setiap sub-objek C yang berjenis berisi deklarasi yang ditunjuk oleh deklarasi using.96) Jika set deklarasi yang dihasilkan tidak semua dari sub-objek dengan tipe yang sama, atau himpunan memiliki anggota nonstatic dan termasuk anggota dari sub-objek yang berbeda, ada ambiguitas dan program tidak berbentuk. Jika tidak, set tersebut adalah hasil dari pencarian.

Ini memiliki yang berikut untuk dikatakan di tempat lain (tepat di atasnya):

Untuk ekspresi-id [ sesuatu seperti "foo" ], pencarian nama dimulai dalam ruang lingkup kelas ini; untuk kualifikasi-id [ sesuatu seperti "A :: foo", A adalah penentu-nama-bersarang ], pencarian nama dimulai dalam lingkup penentu-nama-bersarang. Pencarian nama dilakukan sebelum kontrol akses (3.4, klausul 11).

([...] dimasukkan oleh saya). Perhatikan bahwa meskipun foo Anda di B bersifat pribadi, foo di A tetap tidak akan ditemukan (karena kontrol akses terjadi nanti).

Johannes Schaub - litb
sumber
litb, terima kasih atas jawaban Anda. Tetapi ketika saya mencoba untuk mengkompilasi kode Anda, saya mendapatkan: tidak dapat menyesuaikan akses ke void A::foo(class basic_string<char,char_traits<char>,allocator<char> >)' in kelas B 'karena metode lokal ʻint B :: foo (int)' dengan nama yang sama. Mungkin karena saya menggunakan versi lama gcc
Igor Oks
1
ya, pasti bug kompiler. kompiler lama biasanya menggunakan "A :: foo;" daripada "menggunakan A :: foo;" tapi yang pertama tidak digunakan lagi di C ++.
Johannes Schaub - litb