Seringkali berguna dari perspektif desain untuk dapat menandai hal-hal sebagai tidak berubah. Dengan cara yang sama const
menyediakan penjaga kompiler dan menunjukkan bahwa keadaan tidak boleh berubah, final
dapat 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 final
untuk 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 final
cara 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 final
berguna 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 virtual
dan 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 virtual
langsung 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::f
secara eksplisit menggunakan kata kunci virtual atau tidak , Bar::f
adalah fungsi virtual. Kata virtual
kunci kemudian menjadi opsional dalam hal ini. Jadi mungkin masuk akal untuk Bar::f
dapat ditetapkan sebagai final
, meskipun fungsi virtual ( final
dapat hanya digunakan untuk fungsi virtual).
Dan beberapa orang mungkin lebih suka, secara gaya, untuk secara eksplisit menunjukkan bahwa Bar::f
itu virtual, seperti:
struct Bar: Foo
{
virtual void f() final {...}
};
Bagi saya itu agak berlebihan untuk menggunakan keduanya virtual
dan final
specifier untuk fungsi yang sama dalam konteks ini (juga virtual
dan override
), tapi itu masalah gaya dalam kasus ini. Beberapa orang mungkin menemukan bahwa virtual
mengkomunikasikan sesuatu yang berharga di sini, seperti menggunakan extern
deklarasi fungsi dengan tautan eksternal (meskipun itu opsional tanpa kualifikasi tautan lainnya).