Berapa biaya menggunakan destruktor virtual jika saya menggunakannya bahkan jika itu tidak diperlukan?
Biaya memperkenalkan fungsi virtual apa pun ke kelas (diwarisi atau bagian dari definisi kelas) adalah biaya awal yang mungkin sangat curam (atau tidak tergantung pada objek) dari pointer virtual yang disimpan per objek, seperti:
struct Integer
{
virtual ~Integer() {}
int value;
};
Dalam hal ini, biaya memori relatif besar. Ukuran memori aktual dari instance kelas sekarang akan sering terlihat seperti ini pada arsitektur 64-bit:
struct Integer
{
// 8 byte vptr overhead
int value; // 4 bytes
// typically 4 more bytes of padding for alignment of vptr
};
Totalnya adalah 16 byte untuk Integer
kelas ini dibandingkan dengan hanya 4 byte. Jika kita menyimpan sejuta dari ini dalam sebuah array, kita berakhir dengan 16 megabyte penggunaan memori: dua kali ukuran cache CPU L3 8 MB, dan iterasi melalui array seperti itu berulang kali bisa berkali-kali lebih lambat daripada setara 4 megabyte tanpa penunjuk virtual sebagai akibat dari kesalahan cache tambahan dan kesalahan halaman.
Namun, biaya penunjuk virtual ini per objek, tidak meningkat dengan lebih banyak fungsi virtual. Anda dapat memiliki 100 fungsi anggota virtual di kelas dan overhead per instance masih akan menjadi pointer virtual tunggal.
Pointer virtual biasanya menjadi perhatian yang lebih langsung dari sudut pandang overhead. Namun, selain pointer virtual per instance adalah biaya per kelas. Setiap kelas dengan fungsi virtual menghasilkan vtable
dalam memori yang menyimpan alamat ke fungsi yang seharusnya dipanggil (pengiriman virtual / dinamis) ketika panggilan fungsi virtual dibuat. The vptr
disimpan per contoh maka poin untuk ini kelas khusus vtable
. Overhead ini biasanya menjadi masalah yang lebih kecil, tetapi mungkin mengembang ukuran biner Anda dan menambahkan sedikit biaya runtime jika overhead ini dibayar sia-sia untuk seribu kelas dalam basis kode yang kompleks, misalnya vtable
sisi biaya ini sebenarnya meningkat secara proporsional dengan lebih dan lebih lebih banyak fungsi virtual dalam campuran.
Pengembang Java yang bekerja di area kritis kinerja memahami jenis overhead ini dengan sangat baik (meskipun sering dijelaskan dalam konteks tinju), karena tipe Java yang didefinisikan pengguna secara inherit mewarisi dari object
kelas dasar pusat dan semua fungsi di Jawa secara implisit virtual (dapat ditimpa) ) di alam kecuali ditandai sebaliknya. Akibatnya, Java Integer
juga cenderung membutuhkan 16 byte memori pada platform 64-bit sebagai hasil dari vptr
metadata gaya yang terkait per instance, dan itu biasanya tidak mungkin di Jawa untuk membungkus sesuatu seperti single int
ke dalam kelas tanpa membayar runtime biaya kinerja untuk itu.
Maka pertanyaannya adalah: Mengapa c ++ tidak mengatur semua destruktor virtual secara default?
C ++ benar-benar mendukung kinerja dengan pola pikir "pay as you go" dan juga masih banyak desain yang digerakkan oleh perangkat keras berbasis logam yang diwarisi dari C. Tidak perlu menyertakan biaya overhead yang diperlukan untuk pembuatan vtable dan pengiriman dinamis untuk setiap kelas / instance yang terlibat. Jika kinerja bukan salah satu alasan utama Anda menggunakan bahasa seperti C ++, Anda mungkin mendapat manfaat lebih dari bahasa pemrograman lain di luar sana karena banyak bahasa C ++ kurang aman dan lebih sulit daripada idealnya dengan kinerja yang sering alasan utama untuk menyukai desain seperti itu.
Kapan saya TIDAK perlu menggunakan destruktor virtual?
Cukup sering. Jika suatu kelas tidak dirancang untuk diwariskan, maka ia tidak memerlukan destruktor virtual dan hanya akan membayar biaya overhead yang besar untuk sesuatu yang tidak perlu. Demikian juga, bahkan jika suatu kelas dirancang untuk diwariskan tetapi Anda tidak pernah menghapus instance subtipe melalui basis pointer, maka itu juga tidak memerlukan destruktor virtual. Dalam hal itu, praktik yang aman adalah menentukan destruktor nonvirtual yang dilindungi, seperti:
class BaseClass
{
protected:
// Disallow deleting/destroying subclass objects through `BaseClass*`.
~BaseClass() {}
};
Dalam hal mana saya TIDAK boleh menggunakan destruktor virtual?
Sebenarnya lebih mudah untuk ditutup ketika Anda harus menggunakan destruktor virtual. Kelas yang jauh lebih sering dalam basis kode Anda tidak akan dirancang untuk warisan.
std::vector
, misalnya, tidak dirancang untuk diwarisi dan biasanya tidak boleh diwarisi (desain yang sangat goyah), karena hal itu kemudian akan rentan terhadap masalah penghapusan penunjuk basis ini ( std::vector
sengaja menghindari destruktor virtual) selain masalah kikuk objek yang kikuk jika Anda kelas turunan menambahkan status baru apa pun.
Secara umum kelas yang diwarisi harus memiliki destruktor virtual publik atau yang dilindungi, nonvirtual. Dari C++ Coding Standards
, bab 50:
50. Jadikan kelas dasar destruktor publik dan virtual, atau dilindungi dan nonvirtual. Untuk menghapus, atau tidak menghapus; itulah pertanyaannya: Jika penghapusan melalui pointer ke basis Base harus diizinkan, maka destruktor Base harus bersifat publik dan virtual. Kalau tidak, itu harus dilindungi dan nonvirtual.
Salah satu hal yang C ++ cenderung agak menekankan secara implisit (karena desain cenderung menjadi sangat rapuh dan canggung dan mungkin bahkan tidak aman sebaliknya) adalah gagasan bahwa pewarisan bukan merupakan mekanisme yang dirancang untuk digunakan sebagai renungan. Ini merupakan mekanisme yang dapat diperluas dengan polimorfisme, tetapi yang membutuhkan tinjauan ke depan tentang ke mana kemungkinan diperpanjang. Akibatnya, kelas dasar Anda harus dirancang sebagai akar hierarki warisan di muka, dan bukan sesuatu yang Anda warisi dari nanti sebagai renungan tanpa pandangan jauh ke depan seperti sebelumnya.
Dalam kasus-kasus di mana Anda hanya ingin mewarisi untuk menggunakan kembali kode yang ada, komposisi sering sangat dianjurkan (Prinsip Penggunaan Kembali Komposit).