D-Pointer/bg
This article may require cleanup to meet the Qt Wiki's quality standards. Reason: Auto-imported from ExpressionEngine. Please improve this article if you can. Remove the {{cleanup}} tag and add this page to Updated pages list after it's clean. |
Български English 简体中文 Español
Contents
Какво е d-pointer
Ако някога сте гледали в сорс файловете на Qt, като този например, вероятно сте забелязали, че той е изпълнен с макроситеQ_D
Q_Q
Q_D
Q_Q
Бинарната съвметимост? Какво е това?
Когато правите дизайн на библиотеки като Qt, е желателно приложението, което динамично се свързва с библиотеката, да продължи да работи без прекомпилиране след като тя е обновена/заменена с друга нейна версия. Например, ако вашето приложение CuteApp е било базирано на Qt 4.5, трябва да можете да обновите Qt (на Windows Qt идва с приложението, на Linux често автоматично от пакетният мениджър!) от версия 4.5 до Qt 4.6 и CuteApp трябва да може да се стартира.
Какво разваля бинарната съвместимост?
Така, кога промяна в библиотеката изисква прекомпилиране на приложението? Нека погледнем този прост пример:
class Widget {
…
private:
Rect m_geometry;
};
class Label : public Widget {
…
String text() const { return m_text; }
private:
String m_text;
};
Тук имаме Widget, който има член-данна за своята геометрия. Компилираме нашия Widget и го пускаме в употреба като WidgetLib 1.0.
За WidgetLib 1.1 на някой му хрумва да добави поддръжка на стилове(CSS). Няма проблем! Просто добавяме новите методи и нова член-данна.
class Widget {
…
private:
Rect m_geometry;
String m_stylesheet; // Ново в WidgetLib 1.1
};
class Label : public Widget {
public:
…
String text() const { return m_text; }
private:
String m_text;
};
Пускаме в употреба WidgetLib 1.1 само с горната промяна и виждаме, че CuteApp, компилира и работеща добре с WidgetLib 1.0, се срива грандиозно!
Защо се срива?
Проблема е, че с добавянето на нова член-данна, ние сме променили размера на обектите от тип Widget и Label. Какво значение има това? Когато нашият C++ компилатор генерира код, той използва 'отмествания' за да достъпва данните в обектите.
Ето един доста опростен пример за това как горните POD обекти може да са разположени в паметта.
Разположение на Label в WidgetLib 1.0 | Разположение на Label в WidgetLib 1.1 |
m_geometry <отстъп 0> | m_geometry <offset 0> |
————— | m_stylesheet <отстъп 1> |
m_text <отстъп 1> | —————— |
————— | m_text <отстъп 2> |
Label::text()
text
stylesheet
=== За тези, завършили C++ 101 :-)
Сигурен съм, че в този момент има няколко от вас, които се чудят защо пресмятането на отстъпа наLabel::text()
Label::text()
Label::text()
Label::text()
Никога не променяйте размера на експортиран C++ клас
В заключение, никога не променяйте размера или подредбата(не размествайте данните) на експортирани (т.е видими за потребителя) C++ класове веднъж щом вашата библиотека е пусната за използване. C++ компилатора генерира код като предполага, че размерът и подредбата на данните в класа не се променя след като приложението е било компилирано.
Значи, как можем да добавим нови възможности без да променим размера на обекта?
d-pointer
Номера да запазите константен размера на всички публични класове в библиотеката като пазите само един указател. Този указател сочи към частна/вътрешна структура, която съдържа данните. Размерът на тази вътрешна структура може да се свива или расте без да има странични ефекти върху приложението, защото указателят се достъпва само в библиотеката и от гледна точка на приложението размера на обекта никога не се променя - той е винаги размера да указателя. Този указател се нарича d-pointer.
Духът на този подход е показан в следния код:
/* widget.h */
// Предварителна декларация. Дефиницията ще бъде в widget.cpp или
// в отделен файл, промерно widget_p.h
class WidgetPrivate;
class Widget {
…
Rect geometry() const;
…
private:
// d-pointer никога не се реферира в хедър файла.
// Тъй като WidgetPrivate не е деклариран в този хедър,
// всеки достъп ще бъде грешка при компилацията
WidgetPrivate '''d_ptr;
};
/''' widget_p.h */ (_p означава частен (private) )
struct WidgetPrivate {
Rect geometry;
String stylesheet;
};
/''' widget.cpp */
#include "widget_p.h"
Widget::Widget()
: d_ptr(new WidgetPrivate) // създаване на частните данни {
}
Rect Widget::geoemtry() const {
// d-ptr се достъпва само в кода на библиотеката
return d_ptr->geometry;
}
/''' label.h */
class Label : public Widget {
…
String text();
private:
// всеки клас поддържа свой собствен d-pointer
LabelPrivate '''d_ptr;
};
/''' label.cpp */
// За разлика от WidgetPrivate, дефинираме LabelPrivate в сорс файла
struct LabelPrivate {
String text;
};
Label::Label()
: d_ptr(new LabelPrivate) {
}
String Label::text() {
return d_ptr->text;
}
С горната структура, CuteApp никога недостъпа d-pointer директно. И тъй като d-pointer се достъпва само от WidgetLib и WidgetLib се прекомпилира за всяка версия, Private класа може свободно да се променя, без това да има ефект върху CoolApp.
Други ползи от d-pointer
Не всичко е заради бинарната съвместимост. От d-pointer има и други ползи: Скрива имплементационните детайли - Можем да предоставяме WidgetLib само като хедър файлове и бинаред код. .cpp файловете могат да са затворен код.
- Хедър файла е изчистен от имплементационни детайли и може да служи като справка за API-то на класа.
- Тъй като хедър файловете, необходими за имплементацията са преместени от хедъра в сорс код файла, времето за компилация е доста по-малко.
Вярно е, че горните ползи изглеждат незначително, но реалната причина да се използват d-pointer-и в Qt е бинарната съвместимост и факта, че Qt е започнала като библиотека със затворен код.
q-pointer
До тук, ние разгледахме само d-pointer-и като структура от данни в стил C. В действителност, обаче те съдържат частни методи (помощни функции). Например,LabelPrivate
getLinkTargetFromPoint()
setTextAndUpdateWidget()
Widget::update()
WidgetPrivate
/* widget.h */
// Предварителна декларация. Дефиницията ще бъде в widget.cpp или
// в отделен файл, промерно widget_p.h
class WidgetPrivate;
class Widget {
…
Rect geometry() const;
…
private:
// d-pointer никога не се реферира в хедър файла.
// Тъй като WidgetPrivate не е деклариран в този хедър,
// всеки достъп ще бъде грешка при компилацията
WidgetPrivate '''d_ptr;
};
/''' widget_p.h */ (_p означава частен)
struct WidgetPrivate {
// конструктор, който инизиализира q-ptr
WidgetPrivate(Widget *q) : q_ptr(q) { }
Widget '''q_ptr; // q-ptr, който сочи към клас от API-то
Rect geometry;
String stylesheet;
};
/''' widget.cpp */
#include "widget_p.h"
// създаване на частните данни. Подаване на 'this' указателя за да инициализира q-ptr
Widget::Widget()
: d_ptr(new WidgetPrivate(this)) {
}
Rect Widget::geoemtry() const {
// d-ptr се достъпва само в кода на библиотеката
return d_ptr->geometry;
}
/''' label.h */
class Label : public Widget {
…
String text() const;
private:
LabelPrivate '''d_ptr; // всеки клас поддържа свой собствен d-pointer
};
/''' label.cpp */
// За разлика от WidgetPrivate, дефинираме LabelPrivate в сорс файла
struct LabelPrivate {
LabelPrivate(Label *q) : q_ptr(q) { }
Label '''q_ptr;
String text;
};
Label::Label()
: d_ptr(new LabelPrivate(this)) {
}
String Label::text() {
return d_ptr->text;
}
По-нататъшна оптимизация
В горният код, създаването на един Label води до заделянето на памет заLabelPrivate
WidgetPrivate
QListWidget
Това се решева като се въведе наследяване за нашите частни класове. Така като се създаде клас d-pointer се предава нагоре по йерархията.
/''' widget.h */
class Widget {
public:
Widget();
…
protected:
// само подкласовете могат да достъпват долният код
Widget(WidgetPrivate &d); // позволява на подкласовете да инициализират техните Private класове
WidgetPrivate '''d_ptr;
};
/''' widget_p.h */ (_p означава частен)
struct WidgetPrivate {
WidgetPrivate(Widget *q) : q_ptr(q) { } // конструктор, който инициализира q-ptr
Widget '''q_ptr; // q-ptr, който сочи към API класа
Rect geometry;
String stylesheet;
};
/''' widget.cpp */
Widget::Widget()
: d_ptr(new WidgetPrivate(this)) {
}
Widget::Widget(WidgetPrivate &d)
: d_ptr(&d) {
}
/''' label.h */
class Label : public Widget {
public:
Label();
…
protected:
Label(LabelPrivate &d); // позволява на подкалса Label да предаде своят Private
// забележете, че Label няма d_ptr! Просто използва този на Widget.
};
/''' label.cpp */
#include "widget_p.h" // за да можем да използваме WidgetPrivate
class LabelPrivate : public WidgetPrivate {
public:
String text;
};
Label::Label()
: Widget(*new LabelPrivate) // инициализиране на d-pointer-а с нашият собствен Private {
}
Label::Label(LabelPrivate &d)
: Widget(d) {
}
Label
LabelPrivate
WidgetPrivate
Label
Label
Превръщане на q-ptr и d-ptr в правилният тип (QPTR и DPTR)
Страничен ефек от оптимизацията, която направихме в предната стъпка е, че q-ptr и d-ptr са от типWidget
WidgetPrivate
void Label::setText(const String &text) {
// няма да работи! Тъй като d_ptr е от тип WidgetPrivate, въпреки че сочи към обекто то тип LabelPrivate
d_ptr->text = text;
}
Следователно, когато достъпваме d-pointer в подклас, ние трябва да изпозлваме static_cast, за да превърнем указателя в правилният тип.
void Label::setText(const String &text) {
LabelPrivate '''d = static_cast<LabelPrivate'''>(d_ptr); // превръщане в правилният тип
d->text = text;
}
Както може да видите, не е много приятно да имате static_cast навсякъде из кода. За това можем да си създадем макроси:
// global.h (macros)
#define DPTR (Class) Class##Private '''d = static_cast<Class##Private'''>(d_ptr)
#define QPTR (Class) Class '''q = static_cast<Class'''>(q_ptr)
// label.cpp
void Label::setText(const String &text) {
DPTR (Label);
d->text = text;
}
void LabelPrivate::someHelperFunction() {
QPTR (label);
q->selectAll(); // вече можем да си извикаваме функции в Label
}
d-pointers в Qt
В Qt на практика всеки публичен клас използва d-pointer подхода. Единствените класове, в който той не е използван, са тези, за които е ясно, че никога няма да се наложи да имат допълнителни член-данни. Например, за класове катоQPoint
QRect
- В Qt, основният клас на всички Private обекти е
QObjectPrivate
- Макросите и
Q_D
предоставят функциалността на дискутираните по-горе QPTR и DPTR.Q_Q
- Qt класовете имат макроса в публичният клас. Макросът изглежда така:
Q_DECLARE_PRIVATE
// qlabel.h
class QLabel {
private:
Q_DECLARE_PRIVATE(QLabel);
};
// qglobal.h
#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;
QLabel
d_func()
d_func()
QLabel
QLabel
QLabel
QStatistics
QStatistics
QLabel
QStatistics
label->d_func()->linkClickCount
d_func
Q_DECLARE_PUBLIC