ProgramlamaC++ Geliştiricisi

C++'da sanal tablo (vtable) nedir? Derleyici dinamik çok biçimliliği nasıl uygular ve bu mekanizmayla ilgili hangi incelikler vardır?

Hintsage yapay zeka asistanı ile mülakatları geçin

Cevap.

C++'da dinamik çok biçimlilik, sanal fonksiyonlar mekanizması aracılığıyla özel bir tablo olan vtable (sanal tablo) ile gerçekleştirilir. En az bir sanal fonksiyona sahip her sınıf için derleyici bir vtable oluşturur: bu, sınıfın sanal fonksiyonlarına işaret eden işaretçilerin bir dizisidir. Bu tür bir sınıfın her nesnesi, vtable'a gizli bir işaretçi olan vptr'yi (sanal işaretçi) saklar.

Sanal fonksiyonlara sahip bir sınıf nesnesi oluşturulurken, vptr nesnenin sınıfına karşılık gelen vtable'a ayarlanır. Sanal bir fonksiyon çağrıldığında, çağrı doğrudan değil, vtable'da saklanan adres aracılığıyla yapılır ve nesnenin gerçek türüne bağlı olarak uygun uygulama seçilir (temel türle erişim sağlasa bile).

Örnek:

class Base { public: virtual void foo() { std::cout << "Base::foo() "; } }; class Derived : public Base { public: void foo() override { std::cout << "Derived::foo() "; } }; void call_foo(Base* obj) { obj->foo(); // vtable aracılığıyla çağrı }

foo çağrısı vtable'a erişim olacaktır: eğer Derived sınıfıysa, yeniden tanımlanmış fonksiyon çağrılacaktır.

İncelikler:

  • Eğer sınıf sanal fonksiyon içermezse, vtable ve vptr eklenmez.
  • Sanal yöntemler inline olmaz, genellikle performansı kaybeder.
  • Sınıfın örneğinin boyutuna eklenir (vptr boyutu – genellikle 4/8 bayt).
  • Sanal çağrılar, doğrudan çağrılardan daha yavaştır.

Kandırmaca Sorusu.

"Bir yöntem, virtual olarak ilan edildiyse ama türetilmiş sınıfta yeniden tanımlanmadıysa sanal mı olur? Bu durumda vtable aracılığıyla çağrı çalışır mı?"

Cevap: Evet, eğer bir fonksiyon temel sınıfta virtual olarak ilan edilmişse, türevleri için sanal kalır, yeniden tanımlanmasa bile. Temel işaretçi aracılığıyla çağrı her durumda vtable üzerinden geçecektir. Eğer yöntem yeniden tanımlanmazsa – temel sınıftan bir sürüm çağrılacaktır.

Örnek:

struct Base { virtual void foo() { std::cout << "B "; } }; struct Derived : Base { }; Base* obj = new Derived(); obj->foo(); // "B" yazdırır, ama vtable aracılığıyla çağrı!

Konunun inceliklerini bilmemekten kaynaklanan gerçek hata örnekleri.


Hikaye

Büyük bir projede, sanal fonksiyonları olan temel sınıfta yıkıcıyı sanal olarak ilan etmeyi unuttular. Bu, bellek sızıntılarına neden oldu – temel işaretçi ile silme işlemi yapıldığında yalnızca temel sınıfın yıkıcısı çağrıldı, türevinki değil.


Hikaye

Bir sınıfta yanlışlıkla türetilmiş sınıfta gizlenen sanal bir yöntem ilan edildi (override değil, farklı bir imza/adı ile). Temel sınıf aracılığıyla yapılan çağrılar ilgili fonksiyona ulaşmadı, bu da yanlış çalışmaya yol açtı.


Hikaye

Çoklu kalıtım sonrası vtable'ın güncellenmesi hatalara yol açtı: dynamic_cast kullanırken yanlış yöntem çağrıları, çünkü karmaşık sınıf hiyerarşisinde vptr kayması hesaba katılmadı.