D-Pointer/bg: Difference between revisions

From Qt Wiki
Jump to navigation Jump to search
No edit summary
 
No edit summary
Line 1: Line 1:
'''Български''' [[Dpointer|English]] [[Dpointer-SimplifiedChinese|简体中文]] [[Dpointer Spanish|Español]]
[[Category:Howto]]<br />[[Category:Developing with Qt]]<br />[[Category:QtInternals]]


=Какво е d-pointer=
'''Български''' [[Dpointer|English]] [[Dpointer SimplifiedChinese|简体中文]] [[Dpointer_Spanish|Español]]


Ако някога сте гледали в сорс файловете на Qt, [http://qt.gitorious.com/qt/qt/blobs/master/src/gui/widgets/qlabel.cpp като този например] ''[qt.gitorious.com]'', вероятно сте забелязали, че той е изпълнен с макросите <code>Q_D</code> и <code>Q_Q</code>. Тази статия разкрива целта на тези макроси.
[toc align_right=&quot;yes&amp;quot; depth=&quot;3&amp;quot;]


Макросите <code>Q_D</code> и <code>Q_Q</code> са част от подхода за софтуерен дизайн, наречен ''d-pointer'' (също известен и като [http://en.wikipedia.org/wiki/Opaque_pointer непрозрачен указател] ''[en.wikipedia.org]''), където имплементационните детайли на библиотека са скрити от потребителите и промени по имплементацията могат да се правят без да се нарушава бинарната съвместимост.
= Какво е d-pointer =


==Бинарната съвметимост? Какво е това?==
Ако някога сте гледали в сорс файловете на Qt, &quot;като този например&amp;quot;:http://qt.gitorious.com/qt/qt/blobs/master/src/gui/widgets/qlabel.cpp, вероятно сте забелязали, че той е изпълнен с макросите &lt;code&amp;gt;Q_D&amp;lt;/code&amp;gt; и &lt;code&amp;gt;Q_Q&amp;lt;/code&amp;gt;. Тази статия разкрива целта на тези макроси.
 
Макросите &lt;code&amp;gt;Q_D&amp;lt;/code&amp;gt; и &lt;code&amp;gt;Q_Q&amp;lt;/code&amp;gt; са част от подхода за софтуерен дизайн, наречен ''d-pointer'' (също известен и като &quot;непрозрачен указател&amp;quot;:http://en.wikipedia.org/wiki/Opaque_pointer), където имплементационните детайли на библиотека са скрити от потребителите и промени по имплементацията могат да се правят без да се нарушава бинарната съвместимост.
 
== Бинарната съвметимост? Какво е това? ==


Когато правите дизайн на библиотеки като Qt, е желателно приложението, което динамично се свързва с библиотеката, да продължи да работи без прекомпилиране след като тя е обновена/заменена с друга нейна версия. Например, ако вашето приложение ''CuteApp'' е било базирано на Qt 4.5, трябва да можете да обновите Qt (на Windows Qt идва с приложението, на Linux често автоматично от пакетният мениджър!) от версия 4.5 до Qt 4.6 и CuteApp трябва да може да се стартира.
Когато правите дизайн на библиотеки като Qt, е желателно приложението, което динамично се свързва с библиотеката, да продължи да работи без прекомпилиране след като тя е обновена/заменена с друга нейна версия. Например, ако вашето приложение ''CuteApp'' е било базирано на Qt 4.5, трябва да можете да обновите Qt (на Windows Qt идва с приложението, на Linux често автоматично от пакетният мениджър!) от версия 4.5 до Qt 4.6 и CuteApp трябва да може да се стартира.


===Какво разваля бинарната съвместимост?===
=== Какво разваля бинарната съвместимост? ===


Така, кога промяна в библиотеката изисква прекомпилиране на приложението? Нека погледнем този прост пример:<br />
Така, кога промяна в библиотеката изисква прекомпилиране на приложението? Нека погледнем този прост пример:<br /><code><br /> class Widget {<br /> …<br /> private:<br /> Rect m_geometry;<br /> };
 
class Label : public Widget {<br /> …<br /> String text() const { return m_text; }<br /> private:<br /> String m_text;<br /> };<br /></code>


Тук имаме ''Widget'', който има член-данна за своята геометрия. Компилираме нашия Widget и го пускаме в употреба като '''WidgetLib 1.0'''.
Тук имаме ''Widget'', който има член-данна за своята геометрия. Компилираме нашия Widget и го пускаме в употреба като '''WidgetLib 1.0'''.


За '''WidgetLib 1.1''' на някой му хрумва да добави поддръжка на стилове(<span class="caps">CSS</span>). Няма проблем! Просто добавяме новите методи и нова ''член-данна''.<br />
За '''WidgetLib 1.1''' на някой му хрумва да добави поддръжка на стилове(CSS). Няма проблем! Просто добавяме новите методи и нова ''член-данна''.<br /><code><br /> class Widget {<br /> …<br /> private:<br /> Rect m_geometry;<br /> String m_stylesheet; // Ново в WidgetLib 1.1<br /> };
 
class Label : public Widget {<br /> public:<br /> …<br /> String text() const { return m_text; }<br /> private:<br /> String m_text;<br /> };<br /></code>


Пускаме в употреба WidgetLib 1.1 само с горната промяна и виждаме, че CuteApp, компилира и работеща добре с WidgetLib 1.0, се срива грандиозно!
Пускаме в употреба WidgetLib 1.1 само с горната промяна и виждаме, че CuteApp, компилира и работеща добре с WidgetLib 1.0, се срива грандиозно!


==Защо се срива?==
== Защо се срива? ==


Проблема е, че с добавянето на нова член-данна, ние сме променили размера на обектите от тип Widget и Label. Какво значение има това? Когато нашият C++ компилатор генерира код, той използва ‘отмествания’ за да достъпва данните в обектите.
Проблема е, че с добавянето на нова член-данна, ние сме променили размера на обектите от тип Widget и Label. Какво значение има това? Когато нашият C++ компилатор генерира код, той използва 'отмествания' за да достъпва данните в обектите.


Ето един доста опростен пример за това как горните <span class="caps">POD</span> обекти може да са разположени в паметта.
Ето един доста опростен пример за това как горните POD обекти може да са разположени в паметта.


{| class="infotable line"
{|
| '''Разположение на Label в WidgetLib 1.0'''
|'''Разположение на Label в WidgetLib 1.0'''
| '''Разположение на Label в WidgetLib 1.1'''
|'''Разположение на Label в WidgetLib 1.1'''
|-
|-
| m_geometry &lt;отстъп 0&gt;
|m_geometry &lt;отстъп 0&amp;gt;
| m_geometry &lt;offset 0&gt;
|m_geometry &lt;offset 0&amp;gt;
|-
|-
| ———————
|—————
| m_stylesheet &lt;отстъп 1&gt;
|m_stylesheet &lt;отстъп 1&amp;gt;
|-
|-
| m_text &lt;отстъп 1&gt;
|m_text &lt;отстъп 1&amp;gt;
| ————————-
|——————
|-
|-
| ———————-
|—————
| m_text &lt;отстъп 2&gt;
|m_text &lt;отстъп 2&amp;gt;
|}
|}


В !WidgetLib 1.0, ''m_text'' на Label е била на (логически) отстъп 1. Кодът генериран от компилатора в приложението за метода <code>Label::text()</code> търси текста на отстъп 1 от началото на Label обекта. В WidgetLib 1.1, член-данната ‘‘text’‘ на Label е преместена на (логически) отстъп 2! Тъй като приложението не е било прекомпилирано, то продължава да мисли, че <code>text</code> е на отстъп 1 и така достъпва променливата <code>stylesheet</code>!
В !WidgetLib 1.0, ''m_text'' на Label е била на (логически) отстъп 1. Кодът генериран от компилатора в приложението за метода &lt;code&amp;gt;Label::text()&lt;/code&amp;gt; търси текста на отстъп 1 от началото на Label обекта. В WidgetLib 1.1, член-данната ''text'' на Label е преместена на (логически) отстъп 2! Тъй като приложението не е било прекомпилирано, то продължава да мисли, че &lt;code&amp;gt;text&amp;lt;/code&amp;gt; е на отстъп 1 и така достъпва променливата &lt;code&amp;gt;stylesheet&amp;lt;/code&amp;gt;!
 
===За тези, завършили C++ 101 <span class="smiley">:-)</span>===
 
Сигурен съм, че в този момент има няколко от вас, които се чудят защо пресмятането на отстъпа на <code>Label::text()</code> стана в CuteApp, а не в WidgetLib. Отговорът е, че кода за <code>Label::text()</code> беше дефиниран в хедър файла и компилатора го направи [http://en.wikipedia.org/wiki/Inline_function вмъкната функция] ''[en.wikipedia.org]''.


А тази ситуация променя ли се, ако <code>Label::text()</code> не беше вмъкнат? Да кажем, че <code>Label::text()</code> е беше преместен в сорс файла? Ами, не. C++ компилатора разчита на размера на обектите да бъде еднакъв в по време на компилация и по време на изпълнение. Например, пълненето/изпразването на стека ако създадете обект от тип Label в стека, генерираният от компилатора код ще задели в стека място, базирайки се на размера на Label по време на компилация. Тъй като размера на Label е различен по време на изпълнение в WidgetLib 1.1, конструкторът на Label ще презапише съществуващите данни в стека, което ще доведе до прекратяване на програмата.
=== За тези, завършили C++ 101 :<s>)
<br />Сигурен съм, че в този момент има няколко от вас, които се чудят защо пресмятането на отстъпа на &lt;code&amp;gt;Label::text()&lt;/code&amp;gt; стана в CuteApp, а не в WidgetLib. Отговорът е, че кода за &lt;code&amp;gt;Label::text()&lt;/code&amp;gt; беше дефиниран в хедър файла и компилатора го направи &quot;вмъкната функция&amp;quot;:http://en.wikipedia.org/wiki/Inline_function.
<br />А тази ситуация променя ли се, ако &lt;code&amp;gt;Label::text()&lt;/code&amp;gt; не беше вмъкнат? Да кажем, че &lt;code&amp;gt;Label::text()&lt;/code&amp;gt; е беше преместен в сорс файла? Ами, не. C++ компилатора разчита на размера на обектите да бъде еднакъв в по време на компилация и по време на изпълнение. Например, пълненето/изпразването на стека</s> ако създадете обект от тип Label в стека, генерираният от компилатора код ще задели в стека място, базирайки се на размера на Label по време на компилация. Тъй като размера на Label е различен по време на изпълнение в WidgetLib 1.1, конструкторът на Label ще презапише съществуващите данни в стека, което ще доведе до прекратяване на програмата. ===


==Никога не променяйте размера на експортиран C++ клас==
== Никога не променяйте размера на експортиран C++ клас ==


В заключение, никога не променяйте размера или подредбата(не размествайте данните) на ''експортирани'' (т.е видими за потребителя) C++ класове веднъж щом вашата библиотека е пусната за използване. C++ компилатора генерира код като предполага, че размерът и подредбата на данните в класа ''не се променя след'' като приложението е било компилирано.
В заключение, никога не променяйте размера или подредбата(не размествайте данните) на ''експортирани'' (т.е видими за потребителя) C++ класове веднъж щом вашата библиотека е пусната за използване. C++ компилатора генерира код като предполага, че размерът и подредбата на данните в класа ''не се променя след'' като приложението е било компилирано.
Line 58: Line 64:
Значи, как можем да добавим нови възможности без да променим размера на обекта?
Значи, как можем да добавим нови възможности без да променим размера на обекта?


==d-pointer==
== d-pointer ==


Номера да запазите константен размера на всички публични класове в библиотеката като пазите само един указател. Този указател сочи към частна/вътрешна структура, която съдържа данните. Размерът на тази вътрешна структура може да се свива или расте без да има странични ефекти върху приложението, защото указателят се достъпва само в библиотеката и от гледна точка на приложението размера на обекта никога не се променя той е винаги размера да указателя. Този указател се нарича ''d-pointer''.
Номера да запазите константен размера на всички публични класове в библиотеката като пазите само един указател. Този указател сочи към частна/вътрешна структура, която съдържа данните. Размерът на тази вътрешна структура може да се свива или расте без да има странични ефекти върху приложението, защото указателят се достъпва само в библиотеката и от гледна точка на приложението размера на обекта никога не се променя - той е винаги размера да указателя. Този указател се нарича ''d-pointer''.


Духът на този подход е показан в следния код:
Духът на този подход е показан в следния код:


С горната структура, CuteApp никога недостъпа d-pointer директно. И тъй като ''d-pointer'' се достъпва само от WidgetLib и WidgetLib се прекомпилира за всяка версия, ''Private'' класа може свободно да се променя, без това да има ефект върху CoolApp.
<code><br /> /* widget.h */<br /> // Предварителна декларация. Дефиницията ще бъде в widget.cpp или<br /> // в отделен файл, промерно widget_p.h<br /> class WidgetPrivate;


==Други ползи от d-pointer==
class Widget {<br /> …<br /> Rect geometry() const;<br /> …<br /> private:<br /> // d-pointer никога не се реферира в хедър файла.<br /> // Тъй като WidgetPrivate не е деклариран в този хедър,<br /> // всеки достъп ще бъде грешка при компилацията<br /> WidgetPrivate '''d_ptr;<br /> };
<br /> /''' widget_p.h '''/ (_p означава частен (private) )<br /> struct WidgetPrivate {<br /> Rect geometry;<br /> String stylesheet;<br /> };
<br /> /''' widget.cpp '''/<br /> #include &quot;widget_p.h&amp;quot;<br /> Widget::Widget()<br /> : d_ptr(new WidgetPrivate) // създаване на частните данни {<br /> }
<br /> Rect Widget::geoemtry() const {<br /> // d-ptr се достъпва само в кода на библиотеката<br /> return d_ptr-&gt;geometry;<br /> }
<br /> /''' label.h */<br /> class Label : public Widget {<br /> …<br /> String text();<br /> private:<br /> // всеки клас поддържа свой собствен d-pointer<br /> LabelPrivate '''d_ptr;<br /> };
<br /> /''' label.cpp '''/<br /> // За разлика от WidgetPrivate, дефинираме LabelPrivate в сорс файла<br /> struct LabelPrivate {<br /> String text;<br /> };
<br /> Label::Label()<br /> : d_ptr(new LabelPrivate) {<br /> }
<br /> String Label::text() {<br /> return d_ptr-&gt;text;<br /> }<br /></code>
<br />С горната структура, CuteApp никога недостъпа d-pointer директно. И тъй като ''d-pointer'' се достъпва само от WidgetLib и WidgetLib се прекомпилира за всяка версия, ''Private'' класа може свободно да се променя, без това да има ефект върху CoolApp.
<br />h2. Други ползи от d-pointer
<br />Не всичко е заради бинарната съвместимост. От d-pointer има и други ползи:<br />''' Скрива имплементационните детайли - Можем да предоставяме WidgetLib само като хедър файлове и бинаред код. .cpp файловете могат да са затворен код.<br />* Хедър файла е изчистен от имплементационни детайли и може да служи като справка за API-то на класа.<br />* Тъй като хедър файловете, необходими за имплементацията са преместени от хедъра в сорс код файла, времето за компилация е доста по-малко.


Не всичко е заради бинарната съвместимост. От d-pointer има и други ползи:
Вярно е, че горните ползи изглеждат незначително, но реалната причина да се използват d-pointer-и в Qt е бинарната съвместимост и факта, че Qt е започнала като библиотека със затворен код.


* Скрива имплементационните детайли – Можем да предоставяме WidgetLib само като хедър файлове и бинаред код. .cpp файловете могат да са затворен код.
== q-pointer ==
* Хедър файла е изчистен от имплементационни детайли и може да служи като справка за <span class="caps">API</span>-то на класа.
* Тъй като хедър файловете, необходими за имплементацията са преместени от хедъра в сорс код файла, времето за компилация е доста по-малко.


Вярно е, че горните ползи изглеждат незначително, но реалната причина да се използват d-pointer-и в Qt е бинарната съвместимост и факта, че Qt е започнала като библиотека със затворен код.
До тук, ние разгледахме само d-pointer-и като структура от данни в стил C. В действителност, обаче те съдържат частни методи (помощни функции). Например, &lt;code&amp;gt;LabelPrivate&amp;lt;/code&amp;gt; може да има &lt;code&amp;gt;getLinkTargetFromPoint()&lt;/code&amp;gt; помощна функция, която трябва да взимма линк, когато мишката е натисната. В много случаи, тези помощни функции изискват достъп до публични класове, т.е някои функции от Label или от базовия клас Widget. Например, помощен метод, &lt;code&amp;gt;setTextAndUpdateWidget()&lt;/code&amp;gt;, може да иска да извика &lt;code&amp;gt;Widget::update()&lt;/code&amp;gt;, който е публичен метод за заявка за прерисуване на Widget. Така че, &lt;code&amp;gt;WidgetPrivate&amp;lt;/code&amp;gt; пази указател към публичният клас, който се нарича q-pointer. Като модифицираме горния код, за да ползва q-pointer, получаваме:
 
<code><br /> /* widget.h */<br /> // Предварителна декларация. Дефиницията ще бъде в widget.cpp или<br /> // в отделен файл, промерно widget_p.h<br /> class WidgetPrivate;
 
class Widget {<br /> …<br /> Rect geometry() const;<br /> …<br /> private:<br /> // d-pointer никога не се реферира в хедър файла.<br /> // Тъй като WidgetPrivate не е деклариран в този хедър,<br /> // всеки достъп ще бъде грешка при компилацията<br /> WidgetPrivate '''d_ptr;<br /> };
<br /> /''' widget_p.h */ (_p означава частен)<br /> struct WidgetPrivate {<br /> // конструктор, който инизиализира q-ptr<br /> WidgetPrivate(Widget *q) : q_ptr(q) { }<br /> Widget '''q_ptr; // q-ptr, който сочи към клас от API-то<br /> Rect geometry;<br /> String stylesheet;<br /> };
<br /> /''' widget.cpp '''/<br /> #include &quot;widget_p.h&amp;quot;<br /> // създаване на частните данни. Подаване на 'this' указателя за да инициализира q-ptr<br /> Widget::Widget()<br /> : d_ptr(new WidgetPrivate(this)) {<br /> }
<br /> Rect Widget::geoemtry() const {<br /> // d-ptr се достъпва само в кода на библиотеката<br /> return d_ptr-&gt;geometry;<br /> }
<br /> /''' label.h */<br /> class Label : public Widget {<br /> …<br /> String text() const;<br /> private:<br /> LabelPrivate '''d_ptr; // всеки клас поддържа свой собствен d-pointer<br /> };
<br /> /''' label.cpp */<br /> // За разлика от WidgetPrivate, дефинираме LabelPrivate в сорс файла<br /> struct LabelPrivate {<br /> LabelPrivate(Label *q) : q_ptr(q) { }<br /> Label '''q_ptr;<br /> String text;<br /> };
<br /> Label::Label()<br /> : d_ptr(new LabelPrivate(this)) {<br /> }
<br /> String Label::text() {<br /> return d_ptr-&gt;text;<br /> }<br /></code>
<br />h2. По-нататъшна оптимизация
<br />В горният код, създаването на един Label води до заделянето на памет за &lt;code&amp;gt;LabelPrivate&amp;lt;/code&amp;gt; и &lt;code&amp;gt;WidgetPrivate&amp;lt;/code&amp;gt;. Ако приложим тази стратегия за Qt, ситуацията става доста лоша за класове като &lt;code&amp;gt;QListWidget&amp;lt;/code&amp;gt; - той има 6 нива на вложеност и това ще доведе до 6 заделяния на памет!
<br />Това се решева като се въведе наследяване за нашите ''частни'' класове. Така като се създаде клас d-pointer се предава нагоре по йерархията.
<br /><code><br /> /''' widget.h */<br /> class Widget {<br /> public:<br /> Widget();<br /> …<br /> protected:<br /> // само подкласовете могат да достъпват долният код<br /> Widget(WidgetPrivate &amp;d); // позволява на подкласовете да инициализират техните Private класове<br /> WidgetPrivate '''d_ptr;<br /> };
<br /> /''' widget_p.h */ (_p означава частен)<br /> struct WidgetPrivate {<br /> WidgetPrivate(Widget *q) : q_ptr(q) { } // конструктор, който инициализира q-ptr<br /> Widget '''q_ptr; // q-ptr, който сочи към API класа<br /> Rect geometry;<br /> String stylesheet;<br /> };
<br /> /''' widget.cpp '''/<br /> Widget::Widget()<br /> : d_ptr(new WidgetPrivate(this)) {<br /> }
<br /> Widget::Widget(WidgetPrivate &amp;d)<br /> : d_ptr(&amp;d) {<br /> }
<br /> /''' label.h '''/<br /> class Label : public Widget {<br /> public:<br /> Label();<br /> …<br /> protected:<br /> Label(LabelPrivate &amp;d); // позволява на подкалса Label да предаде своят Private<br /> // забележете, че Label няма d_ptr! Просто използва този на Widget.<br /> };
<br /> /''' label.cpp */<br /> #include &quot;widget_p.h&amp;quot; // за да можем да използваме WidgetPrivate


==q-pointer==
class LabelPrivate : public WidgetPrivate {<br /> public:<br /> String text;<br /> };


До тук, ние разгледахме само d-pointer-и като структура от данни в стил C. В действителност, обаче те съдържат частни методи (помощни функции). Например, <code>LabelPrivate</code> може да има <code>getLinkTargetFromPoint()</code> помощна функция, която трябва да взимма линк, когато мишката е натисната. В много случаи, тези помощни функции изискват достъп до публични класове, т.е някои функции от Label или от базовия клас Widget. Например, помощен метод, <code>setTextAndUpdateWidget()</code>, може да иска да извика <code>Widget::update()</code>, който е публичен метод за заявка за прерисуване на Widget. Така че, <code>WidgetPrivate</code> пази указател към публичният клас, който се нарича q-pointer. Като модифицираме горния код, за да ползва q-pointer, получаваме:
Label::Label()<br /> : Widget(*new LabelPrivate) // инициализиране на d-pointer-а с нашият собствен Private {<br /> }


==По-нататъшна оптимизация==
Label::Label(LabelPrivate &amp;d)<br /> : Widget(d) {<br /> }<br /></code>


В горният код, създаването на един Label води до заделянето на памет за <code>LabelPrivate</code> и <code>WidgetPrivate</code>. Ако приложим тази стратегия за Qt, ситуацията става доста лоша за класове като <code>QListWidget</code> – той има 6 нива на вложеност и това ще доведе до 6 заделяния на памет!
Виждате ли красотата? Сега когато създадем &lt;code&amp;gt;Label&amp;lt;/code&amp;gt; обект, той ще създаде &lt;code&amp;gt;LabelPrivate&amp;lt;/code&amp;gt; ( който е подклас на &lt;code&amp;gt;WidgetPrivate&amp;lt;/code&amp;gt;). Той подава конкретен ''d-pointer'' към защитеният конструктор на Widget! Сега, когато &lt;code&amp;gt;Label&amp;lt;/code&amp;gt; обекта е създаден, има само едно заделяне на памет. Label също има защитен конструктор, който може да се използва от клас, наследяващ &lt;code&amp;gt;Label&amp;lt;/code&amp;gt;, за да предостави свой собствен Private клас.


Това се решева като се въведе наследяване за нашите ''частни'' класове. Така като се създаде клас d-pointer се предава нагоре по йерархията.
== Превръщане на q-ptr и d-ptr в правилният тип (QPTR и DPTR) ==


Виждате ли красотата? Сега когато създадем <code>Label</code> обект, той ще създаде <code>LabelPrivate</code> ( който е подклас на <code>WidgetPrivate</code>). Той подава конкретен ''d-pointer'' към защитеният конструктор на Widget! Сега, когато <code>Label</code> обекта е създаден, има само едно заделяне на памет. Label също има защитен конструктор, който може да се използва от клас, наследяващ <code>Label</code>, за да предостави свой собствен Private клас.
Страничен ефек от оптимизацията, която направихме в предната стъпка е, че q-ptr и d-ptr са от тип &lt;code&amp;gt;Widget&amp;lt;/code&amp;gt; и &lt;code&amp;gt;WidgetPrivate&amp;lt;/code&amp;gt;. Това значи, че следното няма да работи:


==Превръщане на q-ptr и d-ptr в правилният тип (<span class="caps">QPTR</span> и <span class="caps">DPTR</span>)==
<code><br /> void Label::setText(const String &amp;text) {<br /> // няма да работи! Тъй като d_ptr е от тип WidgetPrivate, въпреки че сочи към обекто то тип LabelPrivate<br /> d_ptr-&gt;text = text;<br /> }<br /></code>


Страничен ефек от оптимизацията, която направихме в предната стъпка е, че q-ptr и d-ptr са от тип <code>Widget</code> и <code>WidgetPrivate</code>. Това значи, че следното няма да работи:
Следователно, когато достъпваме d-pointer в подклас, ние трябва да изпозлваме static_cast, за да превърнем указателя в правилният тип.<br /><code><br /> void Label::setText(const String &amp;text) {<br /> LabelPrivate '''d = static_cast&amp;lt;LabelPrivate'''&gt;(d_ptr); // превръщане в правилният тип<br /> d-&gt;text = text;<br /> }<br /></code>


Следователно, когато достъпваме d-pointer в подклас, ние трябва да изпозлваме static_cast, за да превърнем указателя в правилният тип.<br />
Както може да видите, не е много приятно да имате static_cast навсякъде из кода. За това можем да си създадем макроси:<br /><code><br /> // global.h (macros)<br /> #define DPTR (Class) Class##Private '''d = static_cast&amp;lt;Class##Private'''&gt;(d_ptr)<br /> #define QPTR (Class) Class '''q = static_cast&amp;lt;Class'''&gt;(q_ptr)


Както може да видите, не е много приятно да имате static_cast навсякъде из кода. За това можем да си създадем макроси:<br />
// label.cpp<br /> void Label::setText(const String &amp;text) {<br /> DPTR (Label);<br /> d-&gt;text = text;<br /> }


==d-pointers в Qt==
void LabelPrivate::someHelperFunction() {<br /> QPTR (label);<br /> q-&gt;selectAll(); // вече можем да си извикаваме функции в Label<br /> }<br /></code>


В Qt на практика всеки публичен клас използва d-pointer подхода. Единствените класове, в който той не е използван, са тези, за които е ясно, че никога няма да се наложи да имат допълнителни член-данни. Например, за класове като <code>QPoint</code> и <code>QRect</code>, не се очаква да се добавят нови членове, следователно данните се съхраняват директно в класа, вместо да се използва d-pointer.
== d-pointers в Qt ==


* В Qt, основният клас на всички Private обекти е <code>QObjectPrivate</code>
В Qt на практика всеки публичен клас използва d-pointer подхода. Единствените класове, в който той не е използван, са тези, за които е ясно, че никога няма да се наложи да имат допълнителни член-данни. Например, за класове като &lt;code&amp;gt;QPoint&amp;lt;/code&amp;gt; и &lt;code&amp;gt;QRect&amp;lt;/code&amp;gt;, не се очаква да се добавят нови членове, следователно данните се съхраняват директно в класа, вместо да се използва d-pointer.
* Макросите <code>Q_D</code> и <code>Q_Q</code> предоставят функциалността на дискутираните по-горе <span class="caps">QPTR</span> и <span class="caps">DPTR</span>.
* Qt класовете имат макроса <code>Q_DECLARE_PRIVATE</code> в публичният клас. Макросът изглежда така:<br />


Идеята е, че <code>QLabel</code> предостая функция <code>d_func()</code>, която позволява достъп до частният си вътрешен клас. Самият метод е частен (поради това, че макроса е в частната секция на qlabel.h). <code>d_func()</code> обаче може да се извиква от '''приятелски класове''' на <code>QLabel</code>. Това основно е полезно за достъп до информация от Qt класове, които не могат да достъпват информация от <code>QLabel</code> през публичното <span class="caps">API</span>. Като странне пример, <code>QLabel</code> може да пази информация за това колко пъти потребителят е натискал на линк. Обаче няма публично <span class="caps">API</span> за достъп до тази информация. <code>QStatistics</code> е клас, който се нуждае от тази информация. Qt разратотчика ще добави <code>QStatistics</code> като приятел на <code>QLabel</code> и тогава <code>QStatistics</code> ще може да направи <code>label-&gt;d_func()-&gt;linkClickCount</code>.
* В Qt, основният клас на всички Private обекти е &lt;code&amp;gt;QObjectPrivate&amp;lt;/code&amp;gt;
* Макросите &lt;code&amp;gt;Q_D&amp;lt;/code&amp;gt; и &lt;code&amp;gt;Q_Q&amp;lt;/code&amp;gt; предоставят функциалността на дискутираните по-горе QPTR и DPTR.
* Qt класовете имат макроса &lt;code&amp;gt;Q_DECLARE_PRIVATE&amp;lt;/code&amp;gt; в публичният клас. Макросът изглежда така:<br /><code><br /> // qlabel.h<br /> class QLabel {<br /> private:<br /> Q_DECLARE_PRIVATE(QLabel);<br /> };


Друго предимство на <code>d_func</code> е, че налага правилно използване на ''const''. В константен метод на класа MyClass вие имате нужда от Q_D(const MyClass) и по този начин можете да извиквате само константни методи на MyClassPrivate. С “гол” d_ptr може също да извиквате и не константни методи.
// qglobal.h<br /> #define Q_DECLARE_PRIVATE(Class)  inline Class##Private* d_func() { return reinterpret_cast&amp;lt;Class##Private '''&gt;(qGetPtrHelper(d_ptr)); }  inline const Class##Private''' d_func() const {  return reinterpret_cast&amp;lt;const Class##Private *&gt;(qGetPtrHelper(d_ptr)); }  friend class Class##Private;<br /></code>


Също така има и <code>Q_DECLARE_PUBLIC</code>, който се използва в Private класа, за деклариране на публичния клас.
Идеята е, че &lt;code&amp;gt;QLabel&amp;lt;/code&amp;gt; предостая функция &lt;code&amp;gt;d_func()&lt;/code&amp;gt;, която позволява достъп до частният си вътрешен клас. Самият метод е частен (поради това, че макроса е в частната секция на qlabel.h). &lt;code&amp;gt;d_func()&lt;/code&amp;gt; обаче може да се извиква от '''приятелски класове''' на &lt;code&amp;gt;QLabel&amp;lt;/code&amp;gt;. Това основно е полезно за достъп до информация от Qt класове, които не могат да достъпват информация от &lt;code&amp;gt;QLabel&amp;lt;/code&amp;gt; през публичното API. Като странне пример, &lt;code&amp;gt;QLabel&amp;lt;/code&amp;gt; може да пази информация за това колко пъти потребителят е натискал на линк. Обаче няма публично API за достъп до тази информация. &lt;code&amp;gt;QStatistics&amp;lt;/code&amp;gt; е клас, който се нуждае от тази информация. Qt разратотчика ще добави &lt;code&amp;gt;QStatistics&amp;lt;/code&amp;gt; като приятел на &lt;code&amp;gt;QLabel&amp;lt;/code&amp;gt; и тогава &lt;code&amp;gt;QStatistics&amp;lt;/code&amp;gt; ще може да направи &lt;code&amp;gt;label-&gt;d_func()-&gt;linkClickCount&amp;lt;/code&amp;gt;.


===Categories:===
Друго предимство на &lt;code&amp;gt;d_func&amp;lt;/code&amp;gt; е, че налага правилно използване на ''const''. В константен метод на класа MyClass вие имате нужда от Q_D(const MyClass) и по този начин можете да извиквате само константни методи на MyClassPrivate. С &quot;гол&amp;quot; d_ptr може също да извиквате и не константни методи.


* [[:Category:Developing with Qt|Developing_with_Qt]]
Също така има и &lt;code&amp;gt;Q_DECLARE_PUBLIC&amp;lt;/code&amp;gt;, който се използва в Private класа, за деклариране на публичния клас.
* [[:Category:HowTo|HowTo]]
* [[:Category:QtInternals|QtInternals]]

Revision as of 06:17, 24 February 2015



Български English 简体中文 Español

[toc align_right="yes&quot; depth="3&quot;]

Какво е d-pointer

Ако някога сте гледали в сорс файловете на Qt, "като този например&quot;:http://qt.gitorious.com/qt/qt/blobs/master/src/gui/widgets/qlabel.cpp, вероятно сте забелязали, че той е изпълнен с макросите <code&gt;Q_D&lt;/code&gt; и <code&gt;Q_Q&lt;/code&gt;. Тази статия разкрива целта на тези макроси.

Макросите <code&gt;Q_D&lt;/code&gt; и <code&gt;Q_Q&lt;/code&gt; са част от подхода за софтуерен дизайн, наречен d-pointer (също известен и като "непрозрачен указател&quot;:http://en.wikipedia.org/wiki/Opaque_pointer), където имплементационните детайли на библиотека са скрити от потребителите и промени по имплементацията могат да се правят без да се нарушава бинарната съвместимост.

Бинарната съвметимост? Какво е това?

Когато правите дизайн на библиотеки като Qt, е желателно приложението, което динамично се свързва с библиотеката, да продължи да работи без прекомпилиране след като тя е обновена/заменена с друга нейна версия. Например, ако вашето приложение CuteApp е било базирано на Qt 4.5, трябва да можете да обновите Qt (на Windows Qt идва с приложението, на Linux често автоматично от пакетният мениджър!) от версия 4.5 до Qt 4.6 и CuteApp трябва да може да се стартира.

Какво разваля бинарната съвместимост?

Така, кога промяна в библиотеката изисква прекомпилиране на приложението? Нека погледнем този прост пример:

<br /> class Widget {<br /> <br /> private:<br /> Rect m_geometry;<br /> };

class Label : public Widget {<br /> <br /> String text() const { return m_text; }<br /> private:<br /> String m_text;<br /> };<br />

Тук имаме Widget, който има член-данна за своята геометрия. Компилираме нашия Widget и го пускаме в употреба като WidgetLib 1.0.

За WidgetLib 1.1 на някой му хрумва да добави поддръжка на стилове(CSS). Няма проблем! Просто добавяме новите методи и нова член-данна.

<br /> class Widget {<br /> <br /> private:<br /> Rect m_geometry;<br /> String m_stylesheet; // Ново в WidgetLib 1.1<br /> };

class Label : public Widget {<br /> public:<br /> <br /> String text() const { return m_text; }<br /> private:<br /> String m_text;<br /> };<br />

Пускаме в употреба WidgetLib 1.1 само с горната промяна и виждаме, че CuteApp, компилира и работеща добре с WidgetLib 1.0, се срива грандиозно!

Защо се срива?

Проблема е, че с добавянето на нова член-данна, ние сме променили размера на обектите от тип Widget и Label. Какво значение има това? Когато нашият C++ компилатор генерира код, той използва 'отмествания' за да достъпва данните в обектите.

Ето един доста опростен пример за това как горните POD обекти може да са разположени в паметта.

Разположение на Label в WidgetLib 1.0 Разположение на Label в WidgetLib 1.1
m_geometry <отстъп 0&gt; m_geometry <offset 0&gt;
————— m_stylesheet <отстъп 1&gt;
m_text <отстъп 1&gt; ——————
————— m_text <отстъп 2&gt;

В !WidgetLib 1.0, m_text на Label е била на (логически) отстъп 1. Кодът генериран от компилатора в приложението за метода <code&gt;Label::text()</code&gt; търси текста на отстъп 1 от началото на Label обекта. В WidgetLib 1.1, член-данната text на Label е преместена на (логически) отстъп 2! Тъй като приложението не е било прекомпилирано, то продължава да мисли, че <code&gt;text&lt;/code&gt; е на отстъп 1 и така достъпва променливата <code&gt;stylesheet&lt;/code&gt;!

=== За тези, завършили C++ 101 :)
Сигурен съм, че в този момент има няколко от вас, които се чудят защо пресмятането на отстъпа на <code&gt;Label::text()</code&gt; стана в CuteApp, а не в WidgetLib. Отговорът е, че кода за <code&gt;Label::text()</code&gt; беше дефиниран в хедър файла и компилатора го направи "вмъкната функция&quot;:http://en.wikipedia.org/wiki/Inline_function.
А тази ситуация променя ли се, ако <code&gt;Label::text()</code&gt; не беше вмъкнат? Да кажем, че <code&gt;Label::text()</code&gt; е беше преместен в сорс файла? Ами, не. C++ компилатора разчита на размера на обектите да бъде еднакъв в по време на компилация и по време на изпълнение. Например, пълненето/изпразването на стека
ако създадете обект от тип Label в стека, генерираният от компилатора код ще задели в стека място, базирайки се на размера на Label по време на компилация. Тъй като размера на Label е различен по време на изпълнение в WidgetLib 1.1, конструкторът на Label ще презапише съществуващите данни в стека, което ще доведе до прекратяване на програмата. ===

Никога не променяйте размера на експортиран C++ клас

В заключение, никога не променяйте размера или подредбата(не размествайте данните) на експортирани (т.е видими за потребителя) C++ класове веднъж щом вашата библиотека е пусната за използване. C++ компилатора генерира код като предполага, че размерът и подредбата на данните в класа не се променя след като приложението е било компилирано.

Значи, как можем да добавим нови възможности без да променим размера на обекта?

d-pointer

Номера да запазите константен размера на всички публични класове в библиотеката като пазите само един указател. Този указател сочи към частна/вътрешна структура, която съдържа данните. Размерът на тази вътрешна структура може да се свива или расте без да има странични ефекти върху приложението, защото указателят се достъпва само в библиотеката и от гледна точка на приложението размера на обекта никога не се променя - той е винаги размера да указателя. Този указател се нарича d-pointer.

Духът на този подход е показан в следния код:

<br /> /* widget.h */<br /> // Предварителна декларация. Дефиницията ще бъде в widget.cpp или<br /> // в отделен файл, промерно widget_p.h<br /> class WidgetPrivate;

class Widget {<br /> <br /> Rect geometry() const;<br /> <br /> private:<br /> // d-pointer никога не се реферира в хедър файла.<br /> // Тъй като WidgetPrivate не е деклариран в този хедър,<br /> // всеки достъп ще бъде грешка при компилацията<br /> WidgetPrivate '''d_ptr;<br /> };
<br /> /''' widget_p.h '''/ (_p означава частен (private) )<br /> struct WidgetPrivate {<br /> Rect geometry;<br /> String stylesheet;<br /> };
<br /> /''' widget.cpp '''/<br /> #include &quot;widget_p.h&amp;quot;<br /> Widget::Widget()<br /> : d_ptr(new WidgetPrivate) // създаване на частните данни {<br /> }
<br /> Rect Widget::geoemtry() const {<br /> // d-ptr се достъпва само в кода на библиотеката<br /> return d_ptr-&gt;geometry;<br /> }
<br /> /''' label.h */<br /> class Label : public Widget {<br /> <br /> String text();<br /> private:<br /> // всеки клас поддържа свой собствен d-pointer<br /> LabelPrivate '''d_ptr;<br /> };
<br /> /''' label.cpp '''/<br /> // За разлика от WidgetPrivate, дефинираме LabelPrivate в сорс файла<br /> struct LabelPrivate {<br /> String text;<br /> };
<br /> Label::Label()<br /> : d_ptr(new LabelPrivate) {<br /> }
<br /> String Label::text() {<br /> return d_ptr-&gt;text;<br /> }<br />


С горната структура, CuteApp никога недостъпа d-pointer директно. И тъй като d-pointer се достъпва само от WidgetLib и WidgetLib се прекомпилира за всяка версия, Private класа може свободно да се променя, без това да има ефект върху CoolApp.
h2. Други ползи от d-pointer
Не всичко е заради бинарната съвместимост. От d-pointer има и други ползи:
Скрива имплементационните детайли - Можем да предоставяме WidgetLib само като хедър файлове и бинаред код. .cpp файловете могат да са затворен код.
* Хедър файла е изчистен от имплементационни детайли и може да служи като справка за API-то на класа.
* Тъй като хедър файловете, необходими за имплементацията са преместени от хедъра в сорс код файла, времето за компилация е доста по-малко.

Вярно е, че горните ползи изглеждат незначително, но реалната причина да се използват d-pointer-и в Qt е бинарната съвместимост и факта, че Qt е започнала като библиотека със затворен код.

q-pointer

До тук, ние разгледахме само d-pointer-и като структура от данни в стил C. В действителност, обаче те съдържат частни методи (помощни функции). Например, <code&gt;LabelPrivate&lt;/code&gt; може да има <code&gt;getLinkTargetFromPoint()</code&gt; помощна функция, която трябва да взимма линк, когато мишката е натисната. В много случаи, тези помощни функции изискват достъп до публични класове, т.е някои функции от Label или от базовия клас Widget. Например, помощен метод, <code&gt;setTextAndUpdateWidget()</code&gt;, може да иска да извика <code&gt;Widget::update()</code&gt;, който е публичен метод за заявка за прерисуване на Widget. Така че, <code&gt;WidgetPrivate&lt;/code&gt; пази указател към публичният клас, който се нарича q-pointer. Като модифицираме горния код, за да ползва q-pointer, получаваме:

<br /> /* widget.h */<br /> // Предварителна декларация. Дефиницията ще бъде в widget.cpp или<br /> // в отделен файл, промерно widget_p.h<br /> class WidgetPrivate;

class Widget {<br /> <br /> Rect geometry() const;<br /> <br /> private:<br /> // d-pointer никога не се реферира в хедър файла.<br /> // Тъй като WidgetPrivate не е деклариран в този хедър,<br /> // всеки достъп ще бъде грешка при компилацията<br /> WidgetPrivate '''d_ptr;<br /> };
<br /> /''' widget_p.h */ (_p означава частен)<br /> struct WidgetPrivate {<br /> // конструктор, който инизиализира q-ptr<br /> WidgetPrivate(Widget *q) : q_ptr(q) { }<br /> Widget '''q_ptr; // q-ptr, който сочи към клас от API-то<br /> Rect geometry;<br /> String stylesheet;<br /> };
<br /> /''' widget.cpp '''/<br /> #include &quot;widget_p.h&amp;quot;<br /> // създаване на частните данни. Подаване на 'this' указателя за да инициализира q-ptr<br /> Widget::Widget()<br /> : d_ptr(new WidgetPrivate(this)) {<br /> }
<br /> Rect Widget::geoemtry() const {<br /> // d-ptr се достъпва само в кода на библиотеката<br /> return d_ptr-&gt;geometry;<br /> }
<br /> /''' label.h */<br /> class Label : public Widget {<br /> <br /> String text() const;<br /> private:<br /> LabelPrivate '''d_ptr; // всеки клас поддържа свой собствен d-pointer<br /> };
<br /> /''' label.cpp */<br /> // За разлика от WidgetPrivate, дефинираме LabelPrivate в сорс файла<br /> struct LabelPrivate {<br /> LabelPrivate(Label *q) : q_ptr(q) { }<br /> Label '''q_ptr;<br /> String text;<br /> };
<br /> Label::Label()<br /> : d_ptr(new LabelPrivate(this)) {<br /> }
<br /> String Label::text() {<br /> return d_ptr-&gt;text;<br /> }<br />


h2. По-нататъшна оптимизация
В горният код, създаването на един Label води до заделянето на памет за <code&gt;LabelPrivate&lt;/code&gt; и <code&gt;WidgetPrivate&lt;/code&gt;. Ако приложим тази стратегия за Qt, ситуацията става доста лоша за класове като <code&gt;QListWidget&lt;/code&gt; - той има 6 нива на вложеност и това ще доведе до 6 заделяния на памет!
Това се решева като се въведе наследяване за нашите частни класове. Така като се създаде клас d-pointer се предава нагоре по йерархията.


<br /> /''' widget.h */<br /> class Widget {<br /> public:<br /> Widget();<br /> <br /> protected:<br /> // само подкласовете могат да достъпват долният код<br /> Widget(WidgetPrivate &amp;d); // позволява на подкласовете да инициализират техните Private класове<br /> WidgetPrivate '''d_ptr;<br /> };
<br /> /''' widget_p.h */ (_p означава частен)<br /> struct WidgetPrivate {<br /> WidgetPrivate(Widget *q) : q_ptr(q) { } // конструктор, който инициализира q-ptr<br /> Widget '''q_ptr; // q-ptr, който сочи към API класа<br /> Rect geometry;<br /> String stylesheet;<br /> };
<br /> /''' widget.cpp '''/<br /> Widget::Widget()<br /> : d_ptr(new WidgetPrivate(this)) {<br /> }
<br /> Widget::Widget(WidgetPrivate &amp;d)<br /> : d_ptr(&amp;d) {<br /> }
<br /> /''' label.h '''/<br /> class Label : public Widget {<br /> public:<br /> Label();<br /> <br /> protected:<br /> Label(LabelPrivate &amp;d); // позволява на подкалса Label да предаде своят Private<br /> // забележете, че Label няма d_ptr! Просто използва този на Widget.<br /> };
<br /> /''' label.cpp */<br /> #include &quot;widget_p.h&amp;quot; // за да можем да използваме WidgetPrivate

class LabelPrivate : public WidgetPrivate {<br /> public:<br /> String text;<br /> };

Label::Label()<br /> : Widget(*new LabelPrivate) // инициализиране на d-pointer-а с нашият собствен Private {<br /> }

Label::Label(LabelPrivate &amp;d)<br /> : Widget(d) {<br /> }<br />

Виждате ли красотата? Сега когато създадем <code&gt;Label&lt;/code&gt; обект, той ще създаде <code&gt;LabelPrivate&lt;/code&gt; ( който е подклас на <code&gt;WidgetPrivate&lt;/code&gt;). Той подава конкретен d-pointer към защитеният конструктор на Widget! Сега, когато <code&gt;Label&lt;/code&gt; обекта е създаден, има само едно заделяне на памет. Label също има защитен конструктор, който може да се използва от клас, наследяващ <code&gt;Label&lt;/code&gt;, за да предостави свой собствен Private клас.

Превръщане на q-ptr и d-ptr в правилният тип (QPTR и DPTR)

Страничен ефек от оптимизацията, която направихме в предната стъпка е, че q-ptr и d-ptr са от тип <code&gt;Widget&lt;/code&gt; и <code&gt;WidgetPrivate&lt;/code&gt;. Това значи, че следното няма да работи:

<br /> void Label::setText(const String &amp;text) {<br /> // няма да работи! Тъй като d_ptr е от тип WidgetPrivate, въпреки че сочи към обекто то тип LabelPrivate<br /> d_ptr-&gt;text = text;<br /> }<br />

Следователно, когато достъпваме d-pointer в подклас, ние трябва да изпозлваме static_cast, за да превърнем указателя в правилният тип.

<br /> void Label::setText(const String &amp;text) {<br /> LabelPrivate '''d = static_cast&amp;lt;LabelPrivate'''&gt;(d_ptr); // превръщане в правилният тип<br /> d-&gt;text = text;<br /> }<br />

Както може да видите, не е много приятно да имате static_cast навсякъде из кода. За това можем да си създадем макроси:

<br /> // global.h (macros)<br /> #define DPTR (Class) Class##Private '''d = static_cast&amp;lt;Class##Private'''&gt;(d_ptr)<br /> #define QPTR (Class) Class '''q = static_cast&amp;lt;Class'''&gt;(q_ptr)

// label.cpp<br /> void Label::setText(const String &amp;text) {<br /> DPTR (Label);<br /> d-&gt;text = text;<br /> }

void LabelPrivate::someHelperFunction() {<br /> QPTR (label);<br /> q-&gt;selectAll(); // вече можем да си извикаваме функции в Label<br /> }<br />

d-pointers в Qt

В Qt на практика всеки публичен клас използва d-pointer подхода. Единствените класове, в който той не е използван, са тези, за които е ясно, че никога няма да се наложи да имат допълнителни член-данни. Например, за класове като <code&gt;QPoint&lt;/code&gt; и <code&gt;QRect&lt;/code&gt;, не се очаква да се добавят нови членове, следователно данните се съхраняват директно в класа, вместо да се използва d-pointer.

  • В Qt, основният клас на всички Private обекти е <code&gt;QObjectPrivate&lt;/code&gt;
  • Макросите <code&gt;Q_D&lt;/code&gt; и <code&gt;Q_Q&lt;/code&gt; предоставят функциалността на дискутираните по-горе QPTR и DPTR.
  • Qt класовете имат макроса <code&gt;Q_DECLARE_PRIVATE&lt;/code&gt; в публичният клас. Макросът изглежда така:
    <br /> // qlabel.h<br /> class QLabel {<br /> private:<br /> Q_DECLARE_PRIVATE(QLabel);<br /> };
    
    // qglobal.h<br /> #define Q_DECLARE_PRIVATE(Class)  inline Class##Private* d_func() { return reinterpret_cast&amp;lt;Class##Private '''&gt;(qGetPtrHelper(d_ptr)); }  inline const Class##Private''' d_func() const {  return reinterpret_cast&amp;lt;const Class##Private *&gt;(qGetPtrHelper(d_ptr)); }  friend class Class##Private;<br />
    

Идеята е, че <code&gt;QLabel&lt;/code&gt; предостая функция <code&gt;d_func()</code&gt;, която позволява достъп до частният си вътрешен клас. Самият метод е частен (поради това, че макроса е в частната секция на qlabel.h). <code&gt;d_func()</code&gt; обаче може да се извиква от приятелски класове на <code&gt;QLabel&lt;/code&gt;. Това основно е полезно за достъп до информация от Qt класове, които не могат да достъпват информация от <code&gt;QLabel&lt;/code&gt; през публичното API. Като странне пример, <code&gt;QLabel&lt;/code&gt; може да пази информация за това колко пъти потребителят е натискал на линк. Обаче няма публично API за достъп до тази информация. <code&gt;QStatistics&lt;/code&gt; е клас, който се нуждае от тази информация. Qt разратотчика ще добави <code&gt;QStatistics&lt;/code&gt; като приятел на <code&gt;QLabel&lt;/code&gt; и тогава <code&gt;QStatistics&lt;/code&gt; ще може да направи <code&gt;label->d_func()->linkClickCount&lt;/code&gt;.

Друго предимство на <code&gt;d_func&lt;/code&gt; е, че налага правилно използване на const. В константен метод на класа MyClass вие имате нужда от Q_D(const MyClass) и по този начин можете да извиквате само константни методи на MyClassPrivate. С "гол&quot; d_ptr може също да извиквате и не константни методи.

Също така има и <code&gt;Q_DECLARE_PUBLIC&lt;/code&gt;, който се използва в Private класа, за деклариране на публичния клас.