Ganti fungsi virtual C ++ dengan aman

100

Saya memiliki kelas dasar dengan fungsi virtual dan saya ingin mengganti fungsi itu di kelas turunan. Adakah cara untuk membuat compiler memeriksa apakah fungsi yang saya deklarasikan di kelas turunan benar-benar menggantikan fungsi di kelas dasar? Saya ingin menambahkan beberapa makro atau sesuatu yang memastikan bahwa saya tidak secara tidak sengaja mendeklarasikan fungsi baru, alih-alih menimpa yang lama.

Ambil contoh ini:

class parent {
public:
  virtual void handle_event(int something) const {
    // boring default code
  }
};

class child : public parent {
public:
  virtual void handle_event(int something) {
    // new exciting code
  }
};

int main() {
  parent *p = new child();
  p->handle_event(1);
}

Di sini parent::handle_event()dipanggil alih-alih child::handle_event(), karena metode anak melewatkan constdeklarasi dan karenanya mendeklarasikan metode baru. Ini juga bisa menjadi kesalahan ketik pada nama fungsi atau beberapa perbedaan kecil dalam tipe parameter. Ini juga dapat dengan mudah terjadi jika antarmuka kelas dasar berubah dan di suatu tempat beberapa kelas turunan tidak diperbarui untuk mencerminkan perubahan tersebut.

Adakah cara untuk menghindari masalah ini, dapatkah saya memberi tahu kompiler atau alat lain untuk memeriksanya untuk saya? Adakah tanda compiler yang berguna (sebaiknya untuk g ++)? Bagaimana Anda menghindari masalah ini?

sth
sumber
2
Pertanyaan bagus, saya telah mencoba mencari tahu mengapa fungsi kelas anak saya tidak dipanggil sejak satu jam sekarang!
Akash Mankar

Jawaban:

89

Sejak g ++ 4.7, ia memahami overridekata kunci C ++ 11 yang baru :

class child : public parent {
    public:
      // force handle_event to override a existing function in parent
      // error out if the function with the correct signature does not exist
      void handle_event(int something) override;
};
Gunther Piez
sumber
@hirschhornsalz: Saya menemukan bahwa ketika Anda mengimplementasikan fungsi handle_event dan Anda menambahkan override di akhir implementasi fungsi, g ++ memberikan kesalahan; jika Anda menyediakan implementasi fungsi inline dalam deklarasi kelas setelah kata kunci override, semuanya baik-baik saja. Mengapa?
H9uest
3
@ h9uest overrideharus digunakan dalam definisi. Implementasi inline adalah definisi dan implementasi, jadi tidak apa-apa.
Gunther Piez
@hirschhornsalz ya saya mendapat pesan kesalahan yang sama oleh g ++. Namun, catatan tambahan: Anda dan g ++ pesan kesalahan menggunakan istilah "definisi kelas" - bukankah sebaiknya kita menggunakan "deklarasi" (pasangan {deklarasi, definisi})? Anda membuat diri Anda jelas dalam konteks khusus ini dengan mengatakan "definisi & implementasi", tetapi saya hanya ingin tahu mengapa komunitas c ++ tiba-tiba memutuskan untuk mengubah istilah di kelas?
H9uest
20

Sesuatu seperti overridekata kunci C # bukan bagian dari C ++.

Di gcc, -Woverloaded-virtualperingatkan agar tidak menyembunyikan fungsi virtual kelas dasar dengan fungsi dengan nama yang sama tetapi tanda tangan yang cukup berbeda sehingga tidak menimpanya. Namun, ini tidak akan melindungi Anda dari kegagalan mengganti fungsi karena salah mengeja nama fungsi itu sendiri.

CB Bailey
sumber
2
Itu jika Anda kebetulan menggunakan Visual C ++
Steve Rowe
4
Menggunakan Visual C ++ tidak membuat overridekata kunci di C ++; ini mungkin berarti Anda menggunakan sesuatu yang dapat mengkompilasi beberapa kode sumber C ++ yang tidak valid. ;)
CB Bailey
3
Fakta bahwa override tidak valid C ++ berarti standarnya salah, dan bukan Visual C ++
Jon
2
@ Jon: Oke, sekarang saya melihat apa yang Anda tuju. Secara pribadi saya dapat mengambil atau meninggalkan overridefungsionalitas gaya C # ; Saya jarang mengalami masalah dengan penggantian yang gagal dan relatif mudah untuk didiagnosis dan diperbaiki. Satu hal yang tidak saya setujui adalah bahwa pengguna VC ++ harus menggunakannya. Saya lebih suka C ++ terlihat seperti C ++ di semua platform bahkan jika satu proyek tertentu tidak perlu portabel. Perlu dicatat bahwa C ++ 0x akan memiliki [[base_check]], [[override]]dan [[hiding]]atribut sehingga Anda dapat memilih untuk mengganti pemeriksaan jika diinginkan.
CB Bailey
5
Lucunya, beberapa tahun setelah komentar menggunakan VC ++ tidak membuat overridekata kunci yang tampaknya itu tidak . Nah, bukan kata kunci yang tepat tetapi pengenal khusus di C ++ 11. Microsoft mendorong cukup keras untuk membuat kasus khusus ini dan mengikuti format umum untuk atribut dan overridemembuatnya menjadi standar :)
David Rodríguez - dribeas
18

Sejauh yang saya tahu, tidak bisakah Anda membuatnya abstrak?

class parent {
public:
  virtual void handle_event(int something) const = 0 {
    // boring default code
  }
};

Saya pikir saya membaca di www.parashift.com bahwa Anda benar-benar dapat menerapkan metode abstrak. Yang masuk akal bagi saya secara pribadi, satu-satunya hal yang dilakukannya adalah memaksa subclass untuk mengimplementasikannya, tidak ada yang mengatakan apa-apa tentang itu tidak diizinkan untuk memiliki implementasi itu sendiri.

Ray Hidayat
sumber
Baru sekarang saya menyadari ini bekerja pada lebih dari sekedar destruktor! Great ditemukan.
strager
1
Ada beberapa potensi kekurangan untuk ini: 1) hal lain yang menandai satu atau lebih metode sebagai abstrak adalah membuat kelas dasar tidak instantiable, yang mungkin menjadi masalah jika itu bukan bagian dari tujuan penggunaan kelas. 2) kelas dasar mungkin bukan milik Anda untuk dimodifikasi.
Michael Burr
3
Saya setuju dengan Michael Burr. Membuat abstrak kelas dasar bukanlah bagian dari pertanyaan. Sangat masuk akal untuk memiliki kelas dasar dengan fungsionalitas dalam metode virtual, yang Anda ingin timpa oleh kelas turunan. Dan sama masuk akal untuk ingin melindungi dari programmer lain yang mengganti nama fungsi di kelas dasar, dan menyebabkan kelas turunan Anda tidak lagi menimpanya. Ekstensi Microsoft "override" sangat berharga dalam kasus ini. Saya ingin melihatnya ditambahkan ke standar, karena sayangnya tidak ada cara yang baik untuk melakukan ini tanpanya.
Brian
Ini juga mencegah metode dasar (katakanlah BaseClass::method()) dipanggil dalam implementasi turunan (katakanlah DerivedClass::method()), misalnya untuk nilai default.
Narcolessico
11

Di MSVC, Anda dapat menggunakan overridekata kunci CLR meskipun Anda tidak sedang mengkompilasi untuk CLR.

Di g ++, tidak ada cara langsung untuk menerapkannya di semua kasus; orang lain telah memberikan jawaban yang baik tentang cara menangkap perbedaan tanda tangan menggunakan -Woverloaded-virtual. Di versi mendatang, seseorang mungkin menambahkan sintaks seperti __attribute__ ((override))atau yang setara menggunakan sintaks C ++ 0x.

Doug
sumber
9

Di MSVC ++ Anda dapat menggunakan kata kuncioverride

class child : public parent {
public:
  virtual void handle_event(int something) <b>override</b> {
    // new exciting code
  }
};

override berfungsi baik untuk kode asli dan CLR di MSVC ++.

bobobobo
sumber
5

Buat fungsi menjadi abstrak, sehingga kelas turunan tidak memiliki pilihan lain selain menimpanya.

@Ray Kode Anda tidak valid.

class parent {
public:
  virtual void handle_event(int something) const = 0 {
    // boring default code
  }
};

Fungsi abstrak tidak boleh memiliki badan yang ditentukan sebaris. Ini harus dimodifikasi untuk menjadi

class parent {
public:
  virtual void handle_event(int something) const = 0;
};

void parent::handle_event( int something ) { /* do w/e you want here. */ }
Tanveer Badar
sumber
3

Saya akan menyarankan sedikit perubahan dalam logika Anda. Ini mungkin berhasil atau tidak, tergantung pada apa yang perlu Anda capai.

handle_event () masih dapat melakukan "kode default yang membosankan" tetapi alih-alih menjadi virtual, pada titik di mana Anda ingin melakukan "kode baru yang menarik" buatlah kelas dasar memanggil metode abstrak (yaitu harus-ditimpa) metode yang akan diberikan oleh kelas turunan Anda.

EDIT: Dan jika Anda kemudian memutuskan bahwa beberapa kelas turunan Anda tidak perlu memberikan "kode baru yang menarik" maka Anda dapat mengubah abstrak menjadi virtual dan menyediakan implementasi kelas dasar kosong dari fungsionalitas "yang disisipkan".

JMD
sumber
2

Kompilator Anda mungkin memiliki peringatan yang dapat dihasilkan jika fungsi kelas dasar disembunyikan. Jika ya, aktifkan. Itu akan menangkap bentrokan const dan perbedaan dalam daftar parameter. Sayangnya ini tidak akan mengungkap kesalahan ejaan.

Misalnya, ini adalah peringatan C4263 di Microsoft Visual C ++.

Mark Ransom
sumber
1

overrideKata kunci C ++ 11 ketika digunakan dengan deklarasi fungsi di dalam kelas turunan, itu memaksa kompilator untuk memeriksa bahwa fungsi yang dideklarasikan sebenarnya menimpa beberapa fungsi kelas dasar. Jika tidak, kompilator akan menampilkan kesalahan.

Oleh karena itu, Anda dapat menggunakan overridepenentu untuk memastikan polimorfisme dinamis (penggantian fungsi).

class derived: public base{
public:
  virtual void func_name(int var_name) override {
    // statement
  }
};
Adarsh ​​Kumar
sumber