Linux Yazılım Notları

Qt Kütüphanesinde D-Gösterici Kullanımı

Qt kodunda, d-göstericileri yoğun olarak kullanılmakta ve bu amaçla bazı makrolar barındırılmaktadır.

Not: İncelememizde Qt 5.5.0 versiyonunu kullanacağız.

qglobal.h başlık dosyasında tanımlı ilgili makrolar ve fonksiyon şablonları (function template) aşağıdaki gibidir.

template <typename T> static inline T *qGetPtrHelper(T *ptr) { return ptr; }
template <typename Wrapper> static inline typename Wrapper::pointer qGetPtrHelper(const Wrapper &p) { return p.data(); }
#define Q_DECLARE_PRIVATE(Class) \
    inline Class##Private* d_func() { return reinterpret_cast<Class##Private *>(qGetPtrHelper(d_ptr)); } \
    inline const Class##Private* d_func() const { return reinterpret_cast<const Class##Private *>(qGetPtrHelper(d_ptr)); } \
    friend class Class##Private;
#define Q_D(Class) Class##Private * const d = d_func()

Q_DECLARE_PRIVATE ile, biri const olmak üzere, iki tane d_func fonksiyonu tanımlandığını ve sınıfın içsel fonksiyonuna arkadaşlık (friend) verildiğini görüyoruz. d_func fonksiyonunun uygun tür dönüşümü yaparak d-göstericisi adresini döndüğünü hatırlayınız. Makro tanımındaki qGetPtrHelper fonksiyonuna ve d_ptr değişkenine ise daha sonra değineceğiz.

Q_D makrosu ise yazım kolaylığı sağlamakta, d_func geri dönüş değeri d isimli const bir yerel değişkende saklanmaktadır.

Bu makroların, QLabel sınıfındaki örnek bir kullanımı aşağıdaki gibidir.

qlabel.h

private:
    Q_DECLARE_PRIVATE(QLabel)

qlabel.cpp

QLabel::QLabel(QWidget *parent, Qt::WindowFlags f)
    : QFrame(*new QLabelPrivate(), parent, f)
{
    Q_D(QLabel);
    d->init();
}

Bu aşamada d-göstericisi kullanmanın getirdiği bir kısıtlamadan bahsetmek istiyoruz. Daha önce, sınıfların private fonksiyonlarının da içsel sınıfa taşındığından bahsetmiştik. Fakat bu durumda, sınıfın private fonksiyonları public olanları direkt olarak çağıramamaktadır. Bazı durumlarda private fonksiyonların public olanları çağırması istenmektedir. Bu sebeple public sınıfa ait nesnenin adresi, içsel (private) sınıfa geçirilmektedir.

Qt kodunda, içsel sınıf içinde, public sınıf nesnesinin adresini tutan bir gösterici tutulmaktadır. Bu gösterici q-pointer olarak isimlendirilmektedir.

q-pointer

Aşağıdaki şekli inceleyiniz.

Türetme hiyerarşisinin herhangi bir yerindeki bir sınıf, q-göstericisine ulaşmak isteyecektir. Qt bu sebeple, d-göstericisinde olduğu gibi, uygun tür dönüştürme işlemiyle beraber q-göstericisini dönen fonksiyon tanımları içeren makroları barındırmaktdır.

qglobal.h başlık dosyasında tanımlı ilgili makrolar aşağıdaki gibidir.

#define Q_DECLARE_PUBLIC(Class)                                    \
    inline Class* q_func() { return static_cast<Class *>(q_ptr); } \
    inline const Class* q_func() const { return static_cast<const Class *>(q_ptr); } \
    friend class Class;
#define Q_Q(Class) Class * const q = q_func()

Şimdi bütün Qt nesnelerinin temel sınıfı olan QObject sınıfında bu yöntemin nasıl kullanıldığına bakalım. Qt kaynak kodunda, QObject sınıfı için qobject.cpp, qobject.h ve qobject_p.h olmak üzere 3 adet dosyanın bulundurulduğunu görüyoruz. Şimdi bu dosyalarda ilgilendiğimiz kısımlara bakalım.

qobject.h:

class Q_CORE_EXPORT QObjectData {
public:
    ...
    QObject *q_ptr;
    ...
};

class Q_CORE_EXPORT QObject
{
    ...
    Q_DECLARE_PRIVATE(QObject)
    ...
public:
    Q_INVOKABLE explicit QObject(QObject *parent=0);

protected:
    QObject(QObjectPrivate &dd, QObject *parent = 0);

protected:
    QScopedPointer<QObjectData> d_ptr;
    ...
};

Beklediğimiz üzere sınıfın, public ve protected olmak üzere, iki adet başlangıç fonksiyonu bulundurduğunu ve Q_DECLARE_PRIVATE makrosunu kullandığını görüyoruz. d-göstericisi ise d_ptr şeklinde isimlendirilmiş. Q_DECLARE_PRIVATE makrosuna tekrardan bakacak olursak, d_func tanımında d_ptr değişkeninin kullanıldığını görmekteyiz.

Başlık dosyasında ayrıca, q_ptr değişkenini barındıran, QObjectData sınıfının bildirildiğini görüyoruz. QObject sınıfının içsel veri türü QObjectPrivate bu sınıftan türetilmiştir. qobject_p.h dosyasında bu durumu görmekteyiz.

qobject_p.h:

class Q_CORE_EXPORT QObjectPrivate : public QObjectData
{
    Q_DECLARE_PUBLIC(QObject)
    ...
};

d_ptr'nin, içsel sınıf türünden doğal bir gösterici şeklinde değilde bir sınıf türünden tanımlandığına dikkat ediniz. d_ptr bildirimi, daha önceki örneklerimizde kullandığımız şekliyle, aşağıdaki gibi de yapılabilirdi.

    QObjectData *d_ptr;

Fakat bu durumda, d_ptr'nin gösterdiği alanın geri verilmesi, QObject sınıfının sorumluluğunda olacaktı. Burada ise d_ptr bir akıllı gösterici (smart pointer) olarak tanımlanmıştır. Bu sayede QObject nesnesi sonlandığında, içsel verilerin tutulduğu alan da beraberinde sisteme verilecektir.

Not: QScopedPointer, akıllı göstericileri türden bağımsız olarak gerçeklemek için bir sınıf şablonu (template class) olarak yazılmıştır.

Akıllı gösterici sınıflarının, operator*() ve operator->() fonksiyonlarını bulundurduklarını ve bu türden nesnelerin kendi ömürleri sona erdiğinde kullandıkları kaynakları da geri verdiğini hatırlayınız.

d_ptr'nin akıllı gösterici olarak tanımlanması, içsel alanın geri verilmesini kolaylaştırmasına karşın, içsel alan adresine direkt olarak erişimi engellemektedir. Bu sebeple qGetPtrHelper fonksiyon şablonu tanımlanmıştır. Q_DECLARE_PRIVATE makrosundaki qGetPtrHelper kullanımını hatırlayınız.

template <typename Wrapper> static inline typename Wrapper::pointer qGetPtrHelper(const Wrapper &p) { return p.data(); }

qGetPtrHelper ile akıllı gösterici sınıfının data fonksiyonu çağırılmakta ve bu sayede tutulan adrese erişilmektedir.

QScopedPointer sınıf şablonunun ilgili alanları aşağıdaki gibidir. Başlangıç fonksiyonuyla geçirilen adrese data fonksiyonuyla erişildiğini ve bu adres türünün pointer ismiyle typedef edildiğini görüyoruz.

class QScopedPointer
{
    ...
public:
    explicit inline QScopedPointer(T *p = 0) : d(p)
    {
    }
    ...
    inline T *data() const
    {
        return d;
    }
    ...
    typedef T *pointer;
    ...
};

Ayrıca Qt kodunda aşağıdaki gibi bir qGetPtrHelper şablon fonksiyonu daha bulunmaktadır.

template <typename T> static inline T *qGetPtrHelper(T *ptr) { return ptr; }

d_ptr'nin akıllı gösterici yerine, sınıfın içsel veri türü adresinden tanımlanması durumunda bu şablon kullanılacaktır. d_ptr, QObjectData d_ptr* şeklinde tanımlansaydı, derleyici tarafından bu şablon kullanılacaktı. Bu şablon kullanılarak yazılan fonksiyon, kendisine geçirilen değeri geri dönmektedir.

Tekrardan QObject sınıfına dönecek olursak, sırasıyla public ve protected başlangıç fonksiyonlarının aşağıdaki gibi tanımladığını görüyoruz.

qobject.cpp:

QObject::QObject(QObject *parent)
    : d_ptr(new QObjectPrivate)
{
    Q_D(QObject);
    d_ptr->q_ptr = this;
    ...
}
QObject::QObject(QObjectPrivate &dd, QObject *parent)
    : d_ptr(&dd)
{
    Q_D(QObject);
    d_ptr->q_ptr = this;
    ...
}

Beklediğimiz üzere, sınıfın public başlangıç fonksiyonunda, içsel veri alanı için yer tahsis edildiğini, protected başlangıç fonksiyonunda ise türeyen sınıfın geçirdiği adresin kullanıldığını görüyoruz. Her iki fonksiyonda da içsel veri alanına nesnenin adresi geçirilmektedir.

 d_ptr->q_ptr = this;

Not: Türemiş sınıftan taban sınıfa olan adres geçirme işlemlerinin referans yoluyla yapıldığına dikkat ediniz.

Son olarak, QObject sınıfından türeyen QWidget sınıfına bakalım.

QWidget içsel sınıfının QObject içsel sınıfından türetildiğini görüyoruz.

qwidget_p.h:

class Q_WIDGETS_EXPORT QWidgetPrivate : public QObjectPrivate
{
    ...
};

qwidget.cpp içinde tanımlı, public ve protected başlangıç fonksiyonları aşağıdaki gibidir:

qwidget.cpp:

QWidget::QWidget(QWidget *parent, Qt::WindowFlags f)
    : QObject(*new QWidgetPrivate, 0), QPaintDevice()
{
    ...
}
QWidget::QWidget(QWidgetPrivate &dd, QWidget* parent, Qt::WindowFlags f)
    : QObject(dd, 0), QPaintDevice()
{
    ...
}

public başlangıç fonksiyonunda içsel veri alanı için gerekli kaynak alınırken, protected başlangıç fonksiyonunda, QWidget'dan türeyen sınıf örneğinin edindiği adres kullanılmaktadır.

Sonuç olarak, Qt kodunda da daha önce incelediğimiz örneklere benzer şekilde bir kullanım olduğunu, fakat akıllı gösterici ve makro kullanımıyla kodun daha yönetilebilir bir şekilde yazıldığını görmekteyiz.