Apa tujuan final
kata kunci dalam fungsi C ++ 11? Saya memahaminya mencegah overriding fungsi oleh kelas turunan, tetapi jika ini masalahnya, bukankah itu cukup untuk menyatakan sebagai non-virtual final
fungsi Anda ? Apakah ada hal lain yang saya lewatkan di sini?
143
virtual
kata kunci atau tidak.func
bukan virtual, jadi tidak ada yang menimpa dan dengan demikian tidak ada yang menandaioverride
ataufinal
.Jawaban:
Apa yang Anda hilang, sebagai idljarn telah disebutkan dalam komentar adalah bahwa jika Anda meng-override fungsi dari kelas dasar, maka Anda tidak mungkin menandainya sebagai non-virtual:
sumber
virtual
dapat menyebabkan kesalahan, dan C ++ 11 menambahkanoverride
tag ke fungsi yang akan mendeteksi situasi itu dan gagal mengkompilasi ketika fungsi yang dimaksudkan untuk menimpa sebenarnya menyembunyikanIni untuk mencegah kelas dari diwarisi. Dari Wikipedia :
Ini juga digunakan untuk menandai fungsi virtual untuk mencegahnya ditimpa dalam kelas turunan:
Wikipedia lebih lanjut membuat poin menarik :
Itu berarti, yang berikut ini diperbolehkan:
sumber
"final" juga memungkinkan pengoptimalan kompiler untuk mem-bypass panggilan tidak langsung:
dengan "final", kompiler dapat memanggil
CDerived::DoSomething()
langsung dari dalamBlah()
, atau bahkan inline. Tanpa itu, ia harus menghasilkan panggilan tidak langsung di dalamBlah()
karenaBlah()
dapat disebut di dalam kelas turunan yang telah ditimpaDoSomething()
.sumber
Tidak ada yang ditambahkan ke aspek semantik "final".
Tapi saya ingin menambahkan komentar chris green bahwa "final" mungkin menjadi teknik optimisasi kompiler yang sangat penting dalam waktu yang tidak begitu lama. Tidak hanya dalam kasus sederhana yang ia sebutkan, tetapi juga untuk hierarki kelas dunia nyata yang lebih kompleks yang dapat "ditutup" oleh "final", sehingga memungkinkan kompiler untuk menghasilkan kode pengiriman yang lebih efisien daripada dengan pendekatan vtable yang biasa.
Salah satu kelemahan utama dari vtables adalah bahwa untuk objek virtual semacam itu (dengan asumsi 64-bit pada CPU Intel khas) pointer saja memakan 25% (8 dari 64 byte) dari garis cache. Dalam jenis aplikasi yang saya sukai untuk menulis, ini sangat menyakitkan. (Dan dari pengalaman saya itu adalah argumen # 1 terhadap C ++ dari sudut pandang kinerja purist, yaitu oleh programmer C.)
Dalam aplikasi yang membutuhkan kinerja ekstrem, yang tidak biasa untuk C ++, ini mungkin memang menjadi luar biasa, tidak perlu untuk menyelesaikan masalah ini secara manual dalam gaya C atau juggling Template aneh.
Teknik ini dikenal sebagai Devirtualization . Suatu istilah yang patut diingat. :-)
Ada sebuah pidato hebat baru-baru ini oleh Andrei Alexandrescu yang menjelaskan bagaimana Anda dapat mengatasi situasi seperti itu hari ini dan bagaimana "final" mungkin menjadi bagian dari penyelesaian kasus serupa "secara otomatis" di masa depan (dibahas dengan pendengar):
http://channel9.msdn.com/Events/GoingNative/2013/Writing-Quick-Code-in-Cpp-Quickly
sumber
Final tidak dapat diterapkan pada fungsi non-virtual.
Itu tidak akan sangat bermakna untuk dapat menandai metode non-virtual sebagai 'final'. Diberikan
a->foo()
akan selalu meneleponA::foo
.Tetapi, jika A :: foo adalah
virtual
, maka B :: foo akan menimpanya. Ini mungkin tidak diinginkan, dan karenanya masuk akal untuk membuat fungsi virtual menjadi final.Namun pertanyaannya adalah, mengapa memungkinkan final pada fungsi virtual. Jika Anda memiliki hierarki yang dalam:
Kemudian
final
meletakkan 'lantai' pada seberapa banyak overriding dapat dilakukan. Kelas-kelas lain dapat memperluas A dan B dan menimpa merekafoo
, tetapi suatu kelas memperpanjang C maka itu tidak diizinkan.Jadi mungkin tidak masuk akal untuk membuat foo 'top-level'
final
, tetapi mungkin masuk akal lebih rendah.(Saya pikir, ada ruang untuk memperluas kata-kata final dan menimpa ke anggota non-virtual. Mereka akan memiliki arti yang berbeda.)
sumber
final
. Misalnya, jika Anda tahu Anda ingin semuaShape
-foo()
sesuatu yang sudah ditentukan sebelumnya dan pasti bahwa tidak ada bentuk turunan yang harus dimodifikasi. Atau, apakah saya salah dan ada pola yang lebih baik untuk digunakan dalam kasus itu? EDIT: Oh, mungkin karena dalam hal itu, seseorang seharusnya tidak membuat tingkat atasfoo()
virtual
untuk memulai? Tapi tetap saja, itu bisa disembunyikan, bahkan jika dipanggil dengan benar (secara polimorfik) melaluiShape*
...Kasus penggunaan untuk kata kunci 'final' yang saya sukai adalah sebagai berikut:
sumber
final
menambahkan maksud eksplisit agar fungsi Anda tidak ditimpa, dan akan menyebabkan kesalahan kompilator jika ini dilanggar:Sebagai kode berdiri, itu mengkompilasi, dan
B::foo
menimpaA::foo
.B::foo
omong-omong juga virtual. Namun, jika kita mengubah # 1 menjadivirtual int foo() final
, maka ini adalah kesalahan penyusun, dan kami tidak diizinkan untuk menimpanyaA::foo
lebih jauh di kelas turunan.Perhatikan bahwa ini tidak memungkinkan kita untuk "membuka kembali" hierarki baru, yaitu tidak ada cara untuk membuat
B::foo
fungsi baru yang tidak terkait yang dapat secara independen menjadi kepala hirarki virtual baru. Setelah suatu fungsi bersifat final, ia tidak akan pernah dapat dideklarasikan lagi di kelas turunan mana pun.sumber
Kata kunci terakhir memungkinkan Anda untuk mendeklarasikan metode virtual, menimpanya sebanyak N kali, dan kemudian mengamanatkan bahwa 'ini tidak lagi dapat ditimpa'. Ini akan berguna dalam membatasi penggunaan kelas turunan Anda, sehingga Anda dapat mengatakan "Saya tahu kelas super saya memungkinkan Anda menimpa ini, tetapi jika Anda ingin berasal dari saya, Anda tidak bisa!".
Seperti yang ditunjukkan poster lainnya, itu tidak dapat diterapkan pada fungsi non-virtual.
Salah satu tujuan dari kata kunci terakhir adalah untuk mencegah pengesampingan metode yang tidak disengaja. Dalam contoh saya, DoStuff () mungkin merupakan fungsi pembantu yang hanya perlu diubah oleh kelas turunan untuk mendapatkan perilaku yang benar. Tanpa akhir, kesalahan tidak akan ditemukan sampai pengujian.
sumber
Kata kunci terakhir dalam C ++ ketika ditambahkan ke suatu fungsi, mencegahnya ditimpa oleh kelas dasar. Juga ketika ditambahkan ke kelas mencegah pewarisan jenis apa pun. Pertimbangkan contoh berikut yang menunjukkan penggunaan specifier akhir. Program ini gagal dalam kompilasi.
Juga:
sumber
Tambahan untuk jawaban Mario Knezović:
Kode di atas menunjukkan teorinya, tetapi tidak benar-benar diuji pada kompiler nyata. Sangat dihargai jika ada yang menempel output yang dibongkar.
sumber