Dalam C ++, kapan saya harus menggunakan final dalam deklarasi metode virtual?


11

Saya tahu bahwa finalkata kunci digunakan untuk mencegah metode virtual ditimpa oleh kelas turunan. Namun, saya tidak dapat menemukan contoh yang berguna ketika saya harus benar-benar menggunakan finalkata kunci dengan virtualmetode. Terlebih lagi, rasanya seperti penggunaan finaldengan metode virtual adalah bau yang tidak enak karena melarang programmer untuk memperluas kelas di masa depan.

Pertanyaan saya berikutnya:

Apakah ada kasus berguna ketika saya benar-benar harus menggunakan finaldalam virtualdeklarasi metode?


Untuk semua alasan yang sama metode Java dibuat final?
Biasa

Di Jawa semua metode virtual. Metode terakhir di kelas dasar dapat meningkatkan kinerja. C ++ memiliki metode non-virtual, jadi ini bukan kasus untuk bahasa ini. Apakah Anda tahu ada kasus bagus lainnya? Saya ingin mendengarnya. :)
metamaker

Kurasa aku tidak pandai menjelaskan sesuatu - aku mencoba mengatakan hal yang persis sama dengan Mike Nakis.
Biasa

Jawaban:


12

Rekap apa yang dilakukan finalkata kunci: Katakanlah kita memiliki kelas dasar Adan kelas turunan B. Fungsi f()dapat dideklarasikan Asebagai virtual, yang berarti bahwa kelas Bdapat menimpanya. Tetapi kemudian kelas Bmungkin berharap bahwa setiap kelas yang berasal lebih lanjut dari Bseharusnya tidak dapat menimpa f(). Saat itulah kita perlu mendeklarasikan f()seperti finalpada B. Tanpa finalkata kunci, setelah kami mendefinisikan metode sebagai virtual, setiap kelas turunan akan bebas untuk menimpanya. Kata finalkunci digunakan untuk mengakhiri kebebasan ini.

Contoh mengapa Anda membutuhkan hal seperti itu: Misalkan kelas Amendefinisikan fungsi virtual prepareEnvelope(), dan kelas Bmenimpanya dan mengimplementasikannya sebagai urutan panggilan ke metode virtualnya sendiri stuffEnvelope(), lickEnvelope()dan sealEnvelope(). Kelas Bbermaksud untuk mengizinkan kelas turunan untuk menimpa metode virtual ini untuk menyediakan implementasi mereka sendiri, tetapi kelas Btidak ingin mengizinkan kelas turunan untuk menimpa prepareEnvelope()dan dengan demikian mengubah urutan barang, menjilat, menyegel, atau mengabaikan memanggil salah satu dari mereka. Jadi, dalam hal ini kelas Bmenyatakan prepareEnvelope()sebagai final.


Pertama, terima kasih atas jawabannya tetapi bukankah itu tepat seperti yang saya tulis dalam kalimat pertama dari pertanyaan saya? Yang benar-benar saya butuhkan adalah kasus kapan class B might wish that any class which is further derived from B should not be able to override f()? Apakah ada kasus dunia nyata di mana kelas B dan metode f () ada dan cocok?
metamaker

Ya, saya minta maaf, tunggu, saya sedang menulis contoh sekarang.
Mike Nakis

@metamaker di sana Anda pergi.
Mike Nakis

1
Contoh yang sangat bagus, ini adalah jawaban terbaik sejauh ini. Terima kasih!
metamaker

1
Kemudian terima kasih atas waktu yang terbuang. Saya tidak dapat menemukan contoh yang baik di Internet, menghabiskan banyak waktu untuk mencari. Saya akan memberi +1 untuk jawabannya tetapi saya tidak dapat melakukannya karena saya memiliki reputasi rendah. :( Mungkin nanti.
metamaker 7-15

2

Seringkali berguna dari perspektif desain untuk dapat menandai hal-hal sebagai tidak berubah. Dengan cara yang sama constmenyediakan penjaga kompiler dan menunjukkan bahwa keadaan tidak boleh berubah, finaldapat digunakan untuk menunjukkan bahwa perilaku tidak boleh mengubah lebih jauh ke bawah hierarki warisan.

Contoh

Pertimbangkan permainan video di mana kendaraan membawa pemain dari satu lokasi ke lokasi lain. Semua kendaraan harus memeriksa untuk memastikan mereka bepergian ke lokasi yang valid sebelum keberangkatan (pastikan pangkalan di lokasi tidak hancur, misalnya). Kita dapat mulai menggunakan idiom antarmuka non-virtual (NVI) untuk menjamin bahwa pemeriksaan ini dilakukan terlepas dari kendaraan.

class Vehicle
{
public:
    virtual ~Vehicle {}

    bool transport(const Location& location)
    {
        // Mandatory check performed for all vehicle types. We could potentially
        // throw or assert here instead of returning true/false depending on the
        // exceptional level of the behavior (whether it is a truly exceptional
        // control flow resulting from external input errors or whether it's
        // simply a bug for the assert approach).
        if (valid_location(location))
            return travel_to(location);

        // If the location is not valid, no vehicle type can go there.
        return false;
    }

private:
    // Overridden by vehicle types. Note that private access here
    // does not prevent derived, nonfriends from being able to override
    // this function.
    virtual bool travel_to(const Location& location) = 0;
};

Sekarang katakanlah kita memiliki kendaraan terbang dalam permainan kita, dan sesuatu yang semua dan semua kendaraan terbang miliki adalah bahwa mereka harus melalui pemeriksaan inspeksi keselamatan di dalam hangar sebelum tinggal landas.

Di sini kita dapat menggunakan finaluntuk menjamin bahwa semua kendaraan terbang akan melalui inspeksi dan juga mengomunikasikan persyaratan desain kendaraan terbang ini.

class FlyingVehicle: public Vehicle
{
private:
    bool travel_to(const Location& location) final
    {
        // Mandatory check performed for all flying vehicle types.
        if (safety_inspection())
            return fly_to(location);

        // If the safety inspection fails for a flying vehicle, 
        // it will not be allowed to fly to the location.
        return false;
    }

    // Overridden by flying vehicle types.
    virtual void safety_inspection() const = 0;
    virtual void fly_to(const Location& location) = 0;
};

Dengan menggunakan finalcara ini, kita secara efektif memperluas fleksibilitas idiom antarmuka non-virtual untuk memberikan perilaku seragam pada hierarki warisan (bahkan setelahnya, mengatasi masalah kelas dasar yang rapuh) ke fungsi virtual sendiri. Selain itu, kami membeli sendiri ruang gerak untuk membuat perubahan sentral yang memengaruhi semua jenis kendaraan terbang sebagai renungan tanpa memodifikasi masing-masing dan setiap implementasi kendaraan terbang yang ada.

Ini adalah salah satu contoh penggunaan final. Ada konteks yang akan Anda temui di mana itu tidak masuk akal untuk fungsi anggota virtual untuk ditimpa lebih jauh - untuk melakukannya dapat menyebabkan desain yang rapuh dan pelanggaran persyaratan desain Anda.

Di situlah finalberguna dari perspektif desain / arsitektur.

Ini juga berguna dari perspektif pengoptimal karena menyediakan pengoptimal ini informasi desain yang memungkinkannya untuk mendevirtualisasi panggilan fungsi virtual (menghilangkan overhead pengiriman dinamis, dan seringkali lebih signifikan, menghilangkan penghalang optimasi antara pemanggil dan callee).

Pertanyaan

Dari komentar:

Mengapa final dan virtual pernah digunakan pada saat yang sama?

Tidak masuk akal bagi kelas dasar pada akar hierarki untuk mendeklarasikan fungsi sebagai keduanya virtualdan final. Bagi saya itu agak konyol, karena akan membuat baik kompiler maupun pembaca manusia harus melewati rintangan yang tidak perlu yang dapat dihindari dengan hanya menghindari virtuallangsung dalam kasus seperti itu. Namun, subclass mewarisi fungsi anggota virtual seperti:

struct Foo
{
   virtual ~Foo() {}
   virtual void f() = 0;
};

struct Bar: Foo
{
   /*implicitly virtual*/ void f() final {...}
};

Dalam hal ini, apakah Bar::fsecara eksplisit menggunakan kata kunci virtual atau tidak , Bar::fadalah fungsi virtual. Kata virtualkunci kemudian menjadi opsional dalam hal ini. Jadi mungkin masuk akal untuk Bar::fdapat ditetapkan sebagai final, meskipun fungsi virtual ( finaldapat hanya digunakan untuk fungsi virtual).

Dan beberapa orang mungkin lebih suka, secara gaya, untuk secara eksplisit menunjukkan bahwa Bar::fitu virtual, seperti:

struct Bar: Foo
{
   virtual void f() final {...}
};

Bagi saya itu agak berlebihan untuk menggunakan keduanya virtualdan finalspecifier untuk fungsi yang sama dalam konteks ini (juga virtualdan override), tapi itu masalah gaya dalam kasus ini. Beberapa orang mungkin menemukan bahwa virtualmengkomunikasikan sesuatu yang berharga di sini, seperti menggunakan externdeklarasi fungsi dengan tautan eksternal (meskipun itu opsional tanpa kualifikasi tautan lainnya).


Bagaimana Anda berencana untuk mengganti privatemetode di dalam Vehiclekelas Anda ? protectedBukannya yang Anda maksudkan bukan?
Andy

3
@ Davidvider privatetidak mencakup overridding (sedikit kontra-intuitif). Penentu publik / terproteksi / pribadi untuk fungsi virtual hanya berlaku untuk penelepon dan tidak untuk override, dimasukkan dengan kasar. Kelas turunan dapat mengesampingkan fungsi virtual dari kelas dasar tanpa memandang visibilitasnya.

@ Davidvider protectedmungkin lebih masuk akal. Saya lebih suka meraih visibilitas serendah mungkin kapan pun saya bisa. Saya pikir alasan bahasanya dirancang dengan cara ini adalah bahwa, jika tidak, fungsi anggota virtual pribadi tidak akan masuk akal di luar konteks persahabatan, karena tidak ada kelas tetapi seorang teman kemudian akan dapat mengesampingkan mereka jika akses penentu penting dalam konteks mengesampingkan dan tidak hanya menelepon.

Saya pikir setelah diatur ke pribadi tidak akan dikompilasi. Misalnya, baik Java maupun C # tidak mengizinkannya. C ++ mengejutkan saya setiap hari. Bahkan setelah 10 tahun pemrograman.
Andy

@ Davidvider Membuatku tersandung juga ketika saya pertama kali menemukannya. Mungkin saya harus membuatnya protectedagar tidak membingungkan orang lain. Saya akhirnya memberikan komentar, setidaknya, menggambarkan bagaimana fungsi virtual pribadi masih dapat ditimpa.

0
  1. Ini memungkinkan banyak optimisasi, karena dapat diketahui pada waktu kompilasi yang disebut fungsi.

  2. Hati-hati melemparkan kata "kode bau" di sekitar. "Final" tidak membuat tidak mungkin untuk memperpanjang kelas. Klik dua kali pada kata "final", tekan backspace, dan perpanjang kelas. NAMUN akhir adalah dokumentasi yang sangat baik bahwa pengembang tidak mengharapkan Anda untuk mengesampingkan fungsi, dan karena itu pengembang berikutnya harus sangat berhati-hati, karena kelas mungkin berhenti bekerja dengan baik jika metode terakhir ditimpa seperti itu.


Mengapa akan finaldan virtualpernah digunakan pada saat yang sama?
Robert Harvey

1
Ini memungkinkan banyak optimisasi, karena dapat diketahui pada waktu kompilasi yang disebut fungsi. Bisakah Anda menjelaskan bagaimana atau memberikan referensi? NAMUN akhir adalah dokumentasi yang sangat baik bahwa pengembang tidak mengharapkan Anda untuk mengesampingkan fungsi, dan karena itu pengembang berikutnya harus sangat berhati-hati, karena kelas mungkin berhenti bekerja dengan baik jika metode terakhir ditimpa seperti itu. Bukankah itu bau busuk?
metamaker

@RobertHarvey stabilitas ABI. Dalam kasus lain, mungkin harus finaldan override.
Deduplikator

@metamaker Saya memiliki Q yang sama, dibahas dalam komentar di sini - programmers.stackexchange.com/questions/256778/… - & lucu telah menemukan beberapa diskusi lain, hanya sejak bertanya! Pada dasarnya, ini tentang apakah pengoptimal / tautan yang mengoptimalkan dapat menentukan apakah referensi / pointer mungkin mewakili kelas turunan & dengan demikian memerlukan panggilan virtual. Jika metode (atau kelas) terbukti tidak dapat ditimpa di luar tipe statis referensi itu sendiri, mereka dapat devirtualise: panggil langsung. Jika ada salah keraguan - seperti yang sering - mereka harus memanggil hampir. Menyatakan finalbenar-benar dapat membantu mereka
underscore_d
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.