Dalam implementasi C # dan Java, objek biasanya memiliki satu pointer ke kelasnya. Ini dimungkinkan karena mereka adalah bahasa warisan tunggal. Struktur kelas kemudian berisi vtable untuk hierarki warisan tunggal. Tetapi memanggil metode antarmuka memiliki semua masalah warisan ganda juga. Ini biasanya diselesaikan dengan menempatkan vtables tambahan untuk semua antarmuka yang diimplementasikan ke dalam struktur kelas. Ini menghemat ruang dibandingkan dengan implementasi virtual inheritance pada C ++, tetapi membuat pengiriman metode antarmuka lebih rumit - yang sebagian dapat dikompensasi dengan caching.
Misalnya dalam OpenJDK JVM, setiap kelas berisi array vtable untuk semua antarmuka yang diimplementasikan (antarmuka vtable disebut itable ). Ketika suatu metode antarmuka dipanggil, array ini dicari secara linear untuk mengetahui kemungkinan antarmuka itu, maka metode tersebut dapat dikirim melalui yang dapat dijalankan. Caching digunakan sehingga setiap situs panggilan mengingat hasil dari pengiriman metode, jadi pencarian ini hanya harus diulang ketika tipe objek konkret berubah. Kodesemu untuk pengiriman metode:
// Dispatch SomeInterface.method
Method const* resolve_method(
Object const* instance, Klass const* interface, uint itable_slot) {
Klass const* klass = instance->klass;
for (Itable const* itable : klass->itables()) {
if (itable->klass() == interface)
return itable[itable_slot];
}
throw ...; // class does not implement required interface
}
(Bandingkan kode asli di interpreter OpenJDK HotSpot atau kompiler x86 .)
C # (atau lebih tepatnya, CLR) menggunakan pendekatan terkait. Namun, di sini itables tidak berisi pointer ke metode, tetapi adalah slot map: mereka menunjuk ke entri dalam tabel utama kelas. Seperti halnya Java, harus mencari yang benar benar hanya skenario terburuk, dan diharapkan bahwa caching di situs panggilan dapat menghindari pencarian ini hampir selalu. CLR menggunakan teknik yang disebut Virtual Stub Dispatch untuk menambal kode mesin yang dikompilasi JIT dengan strategi caching yang berbeda. Kodesemu:
Method const* resolve_method(
Object const* instance, Klass const* interface, uint interface_slot) {
Klass const* klass = instance->klass;
// Walk all base classes to find slot map
for (Klass const* base = klass; base != nullptr; base = base->base()) {
// I think the CLR actually uses hash tables instead of a linear search
for (SlotMap const* slot_map : base->slot_maps()) {
if (slot_map->klass() == interface) {
uint vtable_slot = slot_map[interface_slot];
return klass->vtable[vtable_slot];
}
}
}
throw ...; // class does not implement required interface
}
Perbedaan utama dengan pseudocode OpenJDK adalah bahwa dalam OpenJDK setiap kelas memiliki array dari semua antarmuka yang diimplementasikan secara langsung atau tidak langsung, sedangkan CLR hanya menyimpan array peta slot untuk antarmuka yang diimplementasikan secara langsung di kelas tersebut. Karena itu, kita perlu menjalankan hierarki warisan ke atas hingga peta slot ditemukan. Untuk hierarki warisan yang dalam, ini menghasilkan penghematan ruang. Ini sangat relevan dalam CLR karena cara bagaimana generik diimplementasikan: untuk spesialisasi generik, struktur kelas disalin dan metode dalam tabel utama dapat digantikan oleh spesialisasi. Peta slot terus menunjuk pada entri vtable yang benar dan karenanya dapat dibagikan di antara semua spesialisasi generik suatu kelas.
Sebagai catatan akhir, ada lebih banyak kemungkinan untuk mengimplementasikan pengiriman antarmuka. Alih-alih menempatkan pointer vtable / itable di objek atau dalam struktur kelas, kita bisa menggunakan pointer lemak ke objek, yang pada dasarnya adalah (Object*, VTable*)
sepasang. Kekurangannya adalah ini menggandakan ukuran pointer dan bahwa upcast (dari tipe beton ke tipe antarmuka) tidak gratis. Tetapi lebih fleksibel, memiliki sedikit tipuan, dan juga berarti bahwa antarmuka dapat diimplementasikan secara eksternal dari suatu kelas. Pendekatan terkait digunakan oleh antarmuka Go, ciri-ciri Rust, dan typeclasses Haskell.
Referensi dan bacaan lebih lanjut:
- Wikipedia: Caching sebaris . Membahas pendekatan caching yang dapat digunakan untuk menghindari pencarian metode yang mahal. Biasanya tidak diperlukan untuk pengiriman berbasis vtable, tetapi sangat diinginkan untuk mekanisme pengiriman yang lebih mahal seperti strategi pengiriman antarmuka di atas.
- OpenJDK Wiki (2013): Panggilan Antarmuka . Membahas itables.
- Pobar, Neward (2009): SSCLI 2.0 Internal. Bab 5 buku ini membahas peta slot dengan sangat rinci. Tidak pernah dipublikasikan tetapi disediakan oleh penulis di blog mereka . The PDF Link sejak pindah. Buku ini mungkin tidak lagi mencerminkan keadaan CLR saat ini.
- CoreCLR (2006): Pengiriman rintisan virtual . Dalam: Book Of The Runtime. Membahas peta slot dan caching untuk menghindari pencarian mahal.
- Kennedy, Syme (2001): Desain dan Implementasi Generik untuk .NET Common Language Runtime . ( Tautan PDF ). Membahas berbagai pendekatan untuk mengimplementasikan obat generik. Generik berinteraksi dengan pengiriman metode karena metode mungkin dikhususkan sehingga vtables mungkin harus ditulis ulang.