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).
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.
"Bir yöntem,
virtualolarak 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ı!
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ı.