Linux Yazılım Notları

Türetme ve D-Göstericisi

Türetme hiyerarşisi içinde, türeyen tüm sınıfların kendi içlerinde ayrı bir d-göstericisi bulundurmaları istenen bir durum değildir.

Türeyen sınıf, içsel veri alanına yeni bir veri elemanı eklemeksizin, sadece taban sınıfın sanal fonksiyonlarını özelleştirebilir (override). Bu durumda, d-göstericisi boşa tutulmuş olacaktır. Ayrıca her sınıfın içsel alanı için bellekte (heap alanında) ayrı bir yer ayrılacak ve bu alanlar gerektiğinde sisteme geri verilecektir. Özellikle türetme hiyerarşinde altlarda bulunan sınıflar için bu yöntem kullanışlı olmayacaktır.

Aşağıdaki şekilde, d-göstericisi kullanan, türemiş bir türe ait nesnenin bellekte kapladığı alan temsil edilmiştir.

Türemiş sınıf nesnesinin içinde aynı zamanda bir taban sınıf nesnesinin bulunduğunu ve ilk olarak taban sınıfa ait başlangıç fonksiyonunun (constructor) çağrıldığını hatırlayınız.

Türemiş sınıf nesnesi içinde, taban ve türemiş sınıf içsel alanlarını gösteren, 2 tane d-göstericisi bulunmaktadır. Bu aşamada bu durum makul görünebilir fakat, daha önce de söylediğimiz gibi, yeni sınıflar türetmeye devam edersek çok sayıda bellek alanı ve d-göstericisi kullanmak zorunda kalacağız.

Türlerin kendisinde olduğu gibi, içsel veri alanı türleri üzerinde de türetme yaparak, tek bir d-göstericisi ve bellek alanı kullanmak mümkündür. Hedeflenen model aşağıdaki şekilde temsil edilmiştir.

Taban sınıflara ait içsel veri türlerinden türetme yapılacağından, artık bu türleri kaynak dosyalarda yazmak mümkün olmayacaktır. Bu yüzden, daha önce de bahsi geçen, genellikle _p.h biten başlık dosyaları kullanılmaktadır.

Şimdi hedeflediğimiz modeli nasıl gerçekleyebileceğimize daha yakından bakalım.

Türetme hiyerarşisinin en üstündeki taban sınıf için gerekli dosyalar ve kod taslağı aşağıdaki gibi olacaktır.

base.h:

class BasePrivate;

class Base {
public:
    Base();
protected:
    BasePrivate *d;
    Base(BasePrivate *d);
};

base_p.h:

class BasePrivate {
public:
    ...
};

base.cpp:

#include "base.h"
#include "base_p.h"

Base::Base() {
    d = new BasePrivate;
}

Base::Base(BasePrivate *d) {
    this->d = d;
}

Sınıfın, public olana ilave olarak, bir de protected başlangıç fonksiyonu (constructor) barındırdığına ve d-göstericisin de yine protected erişime sahip olduğuna dikkat ediniz.

Base sınıfından bir örnek oluşturulduğunda sınıfın public başlangıç fonksiyonu çağırılmakta ve içsel bir veri alanı tahsis edilmektedir.

Base::Base() {
    d = new BasePrivate;
}

Buna karşın, taban sınıftan türeyen bir alt sınıf, ortak bir içsel alan tahsis ederek bu adresi taban sınıfa geçirebilmektedir.

Base::Base(BasePrivate *d) {
    this->d = d;
}

Bir alt sınıf için ise kod taslağını aşağıdaki gibi oluşturabiliriz.

derived.h:

#include "base.h"

class DerivedPrivate;

class Derived : public Base {
public:
    Derived();
protected:
    Derived(DerivedPrivate *d);
};

derived_p.h:

#include "base_p.h"

class DerivedPrivate : public BasePrivate {
public:
    ...
};

derived.cpp:

#include "derived.h"
#include "derived_p.h"

Derived::Derived() : Base(new DerivedPrivate) {

}

Derived::Derived(DerivedPrivate *d) : Base(d) {

}

Türemiş sınıfta bir d-göstericisinin bulunmadığına dikkat ediniz.

Türemiş sınıftan bir örnek oluşturulduğunda, sınıfın public başlangıç fonksiyonu çağırılmakta ve ortak bir içsel veri alanı tahsis edilerek taban sınıfa geçirilmektedir.

Derived::Derived() : Base(new DerivedPrivate) {

}

Derived sınıfından türemiş başka bir sınıf ise, kendi edindiği içsel alan adresini yine protected başlangıç fonksiyonunu kullanarak nihayetinde Base sınıfına geçirebilmektedir.

Derived::Derived(DerivedPrivate *d) : Base(d) {

}

Bu sayede bir sınıf, türetme zincirinin neresinde olursa olsun, kendisi ve türediği tüm sınıflar için gerekli içsel alanı tahsis edip, bu alan adresini tek bir d-göstericisinde saklayabilmektedir. Fakat bu durumda türemiş sınıfların, taban sınıf içindeki d-göstericisine ulaşmaları gerekmektedir.

d-göstericisi protected erişime sahip olmasına karşın, en üstteki içsel sınıf türünden olduğundan, türemiş sınıflar içinde direkt kullanılamaz. Örneğimiz üzerinden gidecek olursak, d-göstericisi BasePrivate* türünden olduğundan, türemiş sınıf içinde bu adres DerivedPrivate* türüne dönüştürülmelidir (type casting). Bu amaçla türemiş sınıflar, tür dönüşüm işini yaparak d-göstericisinin değerini dönen fonksiyonlar barındırmaktadır. Örneğimiz için, Derived sınıfı içinde, böyle bir fonksiyonu aşağıdaki gibi tanımlayabiliriz.

DerivedPrivate *Derived::d_func() {
    return static_cast<DerivedPrivate *>(d);
}

Şimdi bu yöntemin Qt kütüphanesinde nasıl kullanıldığına bakalım.