Merhaba RC Ailesi
Sanal fonksiyonları oluşturmak için C++, sanal tablo olarak bilinen geç (dinamik) bağlamanın özel bir formunu kullanır. Sanal tablo, fonksiyon çağrılarını "geç(dinamik) bağlama" biçiminde çözümlemek için kullanılan bir arama tablosudur. Sanal tablonun diğer adları ise “vtable”, “virtual function table”, “virtual method table” ve “dispatch table” şeklindedir.
Sanal fonksiyonları kullanmak için sanal tabloların nasıl çalıştığını bilmeye gerek olmadığından bu bölümü okumak tamamen opsiyoneldir.
Sanal tablolar oldukça basit olmasına rağmen kelimelerle ifade etmek biraz karmaşıktır. İlk olarak, sanal fonksiyonları kulanan her sınıfa, kendi sanal tablosu verilir. Bu tablo, derleyicinin derleme zamanında ayarladığı statik bir dizidir. Sanal tablo, sınıfın nesnesi tarafından çağrılabilen her bir sanal fonksiyon için bir girdi içerir. Bu tablodaki her girdi, o sınıfın erişebileceği en-türtilmiş fonksiyona işaret eden bir fonksiyon işaretçisidir.
İkinci olarak, derleyici ayrıca taban sınıfa gizli bir işaretçi ekler ve buna *__vptr denir. *__vptr, bir sınıf örneği, o sınıfın sanal tablosunu gösterecek şekilde oluşturulduğunda otomatik olarak ayarlanır. Gerçekte derleyici tarafından self-reference ları çözümlemek için kullanılan bir fonksiyon parametresini olan *this işaretçisinin aksine *__vptr gerçek bir işaretçidir. Bu nedenle, tahsis edilen her sınıf nesnesi, işaretçinin boyutu kadar büyür. Bu ayrıca, *__vptr nin türetilmiş sınıfar tarafından miras alındığı anlamına da gelir.
Buraya kadar kafanız karışmış olabilir, şimdi bir örneğe bakalım;
Burada üç sınıf olduğundan derleyici üç tane sanal tablo ayarlayacak: bir tane Base için ve birer tane D1, D2 için. Derleyici ayrıca sanal fonksiyonları kullanan en temel sınıfa gizli bir işaretçi ekler. Derleyici bunu otomatik yapmasına rağmen bir sonraki örneğimizde bunu kendimiz koyacağız (sadece göstermek amaçlı).
Bir sınıf nesnesi oluşturulduğunda, *__vptr, sınıfın sanal tablosuna işaret edecek şekilde ayarlanır. Örneğin, Base türünün bir nesnesi oluşturulduğunda *__vptr, Base sıfının sanal tablosuna ayarlanacak. D1 veya D2 türünündeki nesneler inşa edildiğinde *__vptr, sırasıyla D1 veya D2 için sanal tabloyu işaret edecek şekilde ayarlanır.
Şimdi bu sanal tabloların nasıl doldurulduğu hakkında konuşalım. Burada sadece iki sanal fonksiyon olduğundan, her sanal tablo iki girdiye sahip olacak (function1 ve function2 için). Sanal tablo doldurulduğunda, her girdi, bu sınıf türünün bir nesnesinin çağırabileceği en-türetilmiş fonksiyonla doldurulur.
Base nesneleri için sanal tablo basittir. Base türünün bir nesnesi sadece Base nin üyelerine erişebilir. Base, D1 ve D2 nin fonksiyonlarına erişemez. Sonuç olarak, girdi function1 için Base::function1() i ve function2 için Base::function2() yi işaret eder.
Sanal tablo D1 için biraz karmaşık görünebilir. D1 türünün bir nesnesi, D1 ve Base fonksiyonlarının her ikisine de erişebilir. Ancak D1, function1 i override ederek Base::function1() yerine D1::function1 i yapmıştır. Sonuç olarak girdi, function1 için D1::function1() i gösterir. D1 function2() yi override etmez, bu nedenle function2() için girdi, Base::function2() yi gösterir.
Bu diyagramda *__vptr, her sınıf için o sınıfın sanal tablosuna işaret eder. Girdiler ise sanal tabloda, sınıfın çağırabileceği fonksiyonların en-türetilmiş haline işaret eder.
D1 türünde bir nesne oluşturduğumuzda ne olacağını düşünelim;
d1 bir D1 nesnesi olduğundan *__vptr işaretçisi D1 sanal tablosuna işaret edecek. Şimidi bir base işaretçisini d1 referansına ayarlayalım;
dPtr işaretçisi bir base türü işaretçi olduğundan d1 nesnesinin base bölümüne işaret eder. Ek olarak *__vptr base bölümünde olduğundan dPtr nin bu işaretçiye erişimi vardır. Bu bağlamda, dPtr-__vptr, D1 sanal tablosuna işaret eder! Sonuç olarak dPtr nin bir base türü olmasına rağmen yine de D1 in sanal tablosuna erişebiliyor. ( __vptr aracılığıyla)
Yani dPtr-function1() i çağırmayı denediğimizde ne oluyor?
İlk olarak program, function1() i bir sanal fonksiyon olarak tanır, İkinci olarak program, dPtr-__vptr işaretçisini D1 in sanal tablosunu almak için kullanır. Son olarak D1 in sanal tablosundan function1() in çağrılabilecek versiyonlarına bakar. Bu bağlamda, dPtr-function1() ifadesi D1::function1() olarak çözümlenir.
Peki dPtr, D1 nesnesi yerine bir Base nesnesine işaret etseydi yine de D1::function1() ifadesini çağırır mıydı? Bunun cevabı hayır!
Bu durumda, b oluşturulduğunda __vptr işaretçisi Base nin sanal tablosuna işaret eder, D1 ininkine değil. Base nin sanal tablosundaki girdi function1() için Base::function1() ifadesine işaret edecek. Bu durumda bPtr-function1() ifadesi Base::function1() olarak çözümlenecek.
Bu sanal tabloları kullanarak derleyici ve program, yalnızca temel sınıfa bir işaretçi veya referans kullanıyor olsanız bile, fonksiyon çağrılarını uygun sanal fonksiyonlara çözümleyebilir.
Sanal fonksiyonları oluşturmak için C++, sanal tablo olarak bilinen geç (dinamik) bağlamanın özel bir formunu kullanır. Sanal tablo, fonksiyon çağrılarını "geç(dinamik) bağlama" biçiminde çözümlemek için kullanılan bir arama tablosudur. Sanal tablonun diğer adları ise “vtable”, “virtual function table”, “virtual method table” ve “dispatch table” şeklindedir.
Sanal fonksiyonları kullanmak için sanal tabloların nasıl çalıştığını bilmeye gerek olmadığından bu bölümü okumak tamamen opsiyoneldir.
Sanal tablolar oldukça basit olmasına rağmen kelimelerle ifade etmek biraz karmaşıktır. İlk olarak, sanal fonksiyonları kulanan her sınıfa, kendi sanal tablosu verilir. Bu tablo, derleyicinin derleme zamanında ayarladığı statik bir dizidir. Sanal tablo, sınıfın nesnesi tarafından çağrılabilen her bir sanal fonksiyon için bir girdi içerir. Bu tablodaki her girdi, o sınıfın erişebileceği en-türtilmiş fonksiyona işaret eden bir fonksiyon işaretçisidir.
İkinci olarak, derleyici ayrıca taban sınıfa gizli bir işaretçi ekler ve buna *__vptr denir. *__vptr, bir sınıf örneği, o sınıfın sanal tablosunu gösterecek şekilde oluşturulduğunda otomatik olarak ayarlanır. Gerçekte derleyici tarafından self-reference ları çözümlemek için kullanılan bir fonksiyon parametresini olan *this işaretçisinin aksine *__vptr gerçek bir işaretçidir. Bu nedenle, tahsis edilen her sınıf nesnesi, işaretçinin boyutu kadar büyür. Bu ayrıca, *__vptr nin türetilmiş sınıfar tarafından miras alındığı anlamına da gelir.
Buraya kadar kafanız karışmış olabilir, şimdi bir örneğe bakalım;

Burada üç sınıf olduğundan derleyici üç tane sanal tablo ayarlayacak: bir tane Base için ve birer tane D1, D2 için. Derleyici ayrıca sanal fonksiyonları kullanan en temel sınıfa gizli bir işaretçi ekler. Derleyici bunu otomatik yapmasına rağmen bir sonraki örneğimizde bunu kendimiz koyacağız (sadece göstermek amaçlı).

Bir sınıf nesnesi oluşturulduğunda, *__vptr, sınıfın sanal tablosuna işaret edecek şekilde ayarlanır. Örneğin, Base türünün bir nesnesi oluşturulduğunda *__vptr, Base sıfının sanal tablosuna ayarlanacak. D1 veya D2 türünündeki nesneler inşa edildiğinde *__vptr, sırasıyla D1 veya D2 için sanal tabloyu işaret edecek şekilde ayarlanır.
Şimdi bu sanal tabloların nasıl doldurulduğu hakkında konuşalım. Burada sadece iki sanal fonksiyon olduğundan, her sanal tablo iki girdiye sahip olacak (function1 ve function2 için). Sanal tablo doldurulduğunda, her girdi, bu sınıf türünün bir nesnesinin çağırabileceği en-türetilmiş fonksiyonla doldurulur.
Base nesneleri için sanal tablo basittir. Base türünün bir nesnesi sadece Base nin üyelerine erişebilir. Base, D1 ve D2 nin fonksiyonlarına erişemez. Sonuç olarak, girdi function1 için Base::function1() i ve function2 için Base::function2() yi işaret eder.
Sanal tablo D1 için biraz karmaşık görünebilir. D1 türünün bir nesnesi, D1 ve Base fonksiyonlarının her ikisine de erişebilir. Ancak D1, function1 i override ederek Base::function1() yerine D1::function1 i yapmıştır. Sonuç olarak girdi, function1 için D1::function1() i gösterir. D1 function2() yi override etmez, bu nedenle function2() için girdi, Base::function2() yi gösterir.

Bu diyagramda *__vptr, her sınıf için o sınıfın sanal tablosuna işaret eder. Girdiler ise sanal tabloda, sınıfın çağırabileceği fonksiyonların en-türetilmiş haline işaret eder.
D1 türünde bir nesne oluşturduğumuzda ne olacağını düşünelim;

d1 bir D1 nesnesi olduğundan *__vptr işaretçisi D1 sanal tablosuna işaret edecek. Şimidi bir base işaretçisini d1 referansına ayarlayalım;

dPtr işaretçisi bir base türü işaretçi olduğundan d1 nesnesinin base bölümüne işaret eder. Ek olarak *__vptr base bölümünde olduğundan dPtr nin bu işaretçiye erişimi vardır. Bu bağlamda, dPtr-__vptr, D1 sanal tablosuna işaret eder! Sonuç olarak dPtr nin bir base türü olmasına rağmen yine de D1 in sanal tablosuna erişebiliyor. ( __vptr aracılığıyla)
Yani dPtr-function1() i çağırmayı denediğimizde ne oluyor?

İlk olarak program, function1() i bir sanal fonksiyon olarak tanır, İkinci olarak program, dPtr-__vptr işaretçisini D1 in sanal tablosunu almak için kullanır. Son olarak D1 in sanal tablosundan function1() in çağrılabilecek versiyonlarına bakar. Bu bağlamda, dPtr-function1() ifadesi D1::function1() olarak çözümlenir.
Peki dPtr, D1 nesnesi yerine bir Base nesnesine işaret etseydi yine de D1::function1() ifadesini çağırır mıydı? Bunun cevabı hayır!

Bu durumda, b oluşturulduğunda __vptr işaretçisi Base nin sanal tablosuna işaret eder, D1 ininkine değil. Base nin sanal tablosundaki girdi function1() için Base::function1() ifadesine işaret edecek. Bu durumda bPtr-function1() ifadesi Base::function1() olarak çözümlenecek.
Bu sanal tabloları kullanarak derleyici ve program, yalnızca temel sınıfa bir işaretçi veya referans kullanıyor olsanız bile, fonksiyon çağrılarını uygun sanal fonksiyonlara çözümleyebilir.