D-Pointer/ja: Difference between revisions

From Qt Wiki
Jump to navigation Jump to search
(Draft #6)
 
(translated an untranslated string)
 
(2 intermediate revisions by the same user not shown)
Line 95: Line 95:
WidgetLib 1.0では、ラベルのテキストメンバは(論理的)オフセット1の場所にあります。コンパイラがアプリケーション中に生成したコードでは、<tt>Label::text()</tt>メソッドは、アプリケーション中のラベルオブジェクトから1だけオフセットした位置にアクセスするものと翻訳されるのです。しかし、WidgetLib 1.1では、ラベルの'''テキスト'''メンバは(論理的)オフセット2にシフトしてしまっています!アプリケーションは、再コンパイルされていないため<tt>text</tt>がオフセット1にあるものと考えていますので、<tt>stylesheet</tt>変数にアクセスしてしまうのです!
WidgetLib 1.0では、ラベルのテキストメンバは(論理的)オフセット1の場所にあります。コンパイラがアプリケーション中に生成したコードでは、<tt>Label::text()</tt>メソッドは、アプリケーション中のラベルオブジェクトから1だけオフセットした位置にアクセスするものと翻訳されるのです。しかし、WidgetLib 1.1では、ラベルの'''テキスト'''メンバは(論理的)オフセット2にシフトしてしまっています!アプリケーションは、再コンパイルされていないため<tt>text</tt>がオフセット1にあるものと考えていますので、<tt>stylesheet</tt>変数にアクセスしてしまうのです!


I am sure at this point there are a few who are wondering why the <tt>Label::text()</tt>'s offset calculation code ended up in the CuteApp binary and not in the WidgetLib binary. The answer is that the code for <tt>Label::text()</tt> was defined in the header file and the compiler ended up ''[http://en.wikipedia.org/wiki/Inline_function inlining]'' it.
ここで、なぜ<tt>Label::text()</tt>のオフセット計算コードがCuteAppバイナリで終わってしまって、WidgetLibバイナリで終わらないのかと不思議に思う人がいるはずです。その答えは、<tt>Label::text()</tt>用のコードがヘッダファイルで定義されているので、コンパイラがそれを[https://ja.wikipedia.org/wiki/インライン関数 インライン化]するだけで終わってしまうからです。


So, does the situation change if <tt>Label::text()</tt> had not been inlined? Say, <tt>Label::text()</tt> was moved to the source file? Well, no. The C++ compiler relies on the size of objects being the same at compile time and run-time. For example, stack winding/unwinding - if you created a Label object on the stack, the compiler generated code to allocate space on the stack based on the Label's size at compile time. Since the size of Label is different at run time in WidgetLib 1.1, Label's constructor overwrites existing stack data and ends up corrupting the stack.
それでは、<tt>Label::text()</tt>がインライン化されていなければ事情は変わるのでしょうか。つまり、<tt>Label::text()</tt>をソースファイルに移動したとしたら?答えはノーです。C++コンパイラは、オブジェクトのサイズがコンパイル時と実行時で同じであることに依存しています。たとえば、スタックワインディング・アンワインディングがそうです。もしラベルオブジェクトをスタックに作成したとすると、コンパイラはスタックに領域を割り当てるのに、コンパイル時のラベルサイズを用いるコードを生成します。WidgetLib 1.1では、実行時のラベルサイズが違うために、ラベルのコンストラクタは既存のスタックデータを上書きしてしまい、スタックの破損をもたらすのです。
 
===エクスポートされたC++クラスのサイズは決して変更してはならない===
===Never change the size of an exported C++ class===
要約すると、あなたのライブラリが一旦リリースされたならば、(ユーザーに見える形で)<u>エクスポートされた</u>C++クラスのサイズやレイアウト(データを動かし回ってはいけません)は、けっして変更してはなりません。C++コンパイラは、クラス中のデータのサイズや順序は、アプリケーションがコンパイルされた<u>後は</u>変更されないと仮定してコードを生成します。
 
In summary, never ever change the size or layout (don't move the data around) of ''exported'' (i.e visible to the user) C++ classes once your library has been released. A C++ compiler generates code assuming that the size or the ordering of data in a class does not change ''after'' the application has been compiled.
 
So, how can one not change the size of the object and yet add new features?


すると、どうやって、オブジェクトのサイズを変更せずに、新機能を追加できるでしょうか。
==dポインタ==
==dポインタ==


Line 114: Line 111:


<pre>
<pre>
/* Since d_ptr is a pointer and is never referended in header file
/* d_ptrはポインタであり、ヘッダファイル中で参照されることは決してないので
(it would cause a compile error) WidgetPrivate doesn't have to be included,
  (その場合コンパイルエラーとなります)、WidgetPrivateをインクルードする
but forward-declared instead.
  必要はありませんが、代わりに前方宣言する必要があります。
The definition of the class can be written in widget.cpp or
  クラスの定義は、widget.cppに書くこともできますし、widget_p.hなどの
in a separate file, say widget_p.h */
  別ファイルに書くこともできます。 */
   
   
  class WidgetPrivate;
  class WidgetPrivate;
Line 136: Line 133:


<pre>
<pre>
/* widget_p.h (_p means private) */
/* widget_p.h (_p private を表すことにします) */
struct WidgetPrivate
struct WidgetPrivate
{
{
Line 147: Line 144:


<pre>
<pre>
// With this #include, we can access WidgetPrivate.
// この#includeのおかげで、WidgetPrivateにアクセスできます。
#include "widget_p.h"
#include "widget_p.h"


Widget::Widget() : d_ptr(new WidgetPrivate)
Widget::Widget() : d_ptr(new WidgetPrivate)
{
{
     // Creation of private data
     // 非公開データの生成
}
}


Rect Widget::geometry() const
Rect Widget::geometry() const
{
{
     // The d-ptr is only accessed in the library code
     // d-ptrはライブラリコード内でのみアクセスされます
     return d_ptr->geometry;
     return d_ptr->geometry;
}
}
Line 173: Line 170:


private:
private:
     // Each class maintains its own d-pointer
     // 各クラスは、それぞれのdポインタを保持します
     LabelPrivate *d_ptr;
     LabelPrivate *d_ptr;
};
};
Line 181: Line 178:


<pre>
<pre>
// Unlike WidgetPrivate, the author decided LabelPrivate
// WidgetPrivateとは違って、執筆者はLabelPrivateを
// to be defined in the source file itself
// ソースファイル自身の中で定義することに決めました
struct LabelPrivate
struct LabelPrivate
{
{
Line 234: Line 231:
struct WidgetPrivate
struct WidgetPrivate
{
{
     // Constructor that initializes the q-ptr
     // q-ptrを初期化するコンストラクタ
     WidgetPrivate(Widget *q) : q_ptr(q) { }
     WidgetPrivate(Widget *q) : q_ptr(q) { }
     Widget *q_ptr; // q-ptr points to the API class
     Widget *q_ptr; // q-ptrはAPIクラスをポイントしています
     Rect geometry;
     Rect geometry;
     String stylesheet;
     String stylesheet;
Line 246: Line 243:
<pre>
<pre>
#include "widget_p.h"
#include "widget_p.h"
// Create private data.
// 非公開データを生成
// Pass the 'this' pointer to initialize the q-ptr
// thisポインタをq-ptrを初期化するために渡します
Widget::Widget() : d_ptr(new WidgetPrivate(this))
Widget::Widget() : d_ptr(new WidgetPrivate(this))
{
{
Line 254: Line 251:
Rect Widget::geometry() const
Rect Widget::geometry() const
{
{
     // the d-ptr is only accessed in the library code
     // d-ptrはライブラリコード中のみでアクセスされます
     return d_ptr->geometry;
     return d_ptr->geometry;
}
}
Line 277: Line 274:


<pre>
<pre>
// Unlike WidgetPrivate, the author decided LabelPrivate
// WidgetPrivateとは違って、執筆者はLabelPrivateを
// to be defined in the source file itself
// ソースファイル自身の中で定義することに決めました
struct LabelPrivate
struct LabelPrivate
{
{
Line 313: Line 310:
     // ...
     // ...
protected:
protected:
     // only subclasses may access the below
     // サブクラスのみ以下にアクセスできます
     // allow subclasses to initialize with their own concrete Private
     // サブクラスにそれら自身の具体的なPrivateを初期化するのを許容します
     Widget(WidgetPrivate &d);
     Widget(WidgetPrivate &d);
     WidgetPrivate *d_ptr;
     WidgetPrivate *d_ptr;
Line 325: Line 322:
struct WidgetPrivate
struct WidgetPrivate
{
{
     WidgetPrivate(Widget *q) : q_ptr(q) { } // constructor that initializes the q-ptr
     WidgetPrivate(Widget *q) : q_ptr(q) { } // q-ptrを初期化するコンストラクタ
     Widget *q_ptr; // q-ptr that points to the API class
     Widget *q_ptr; // APIクラスをポイントするq-ptr
     Rect geometry;
     Rect geometry;
     String stylesheet;
     String stylesheet;
Line 353: Line 350:
     // ...
     // ...
protected:
protected:
     Label(LabelPrivate &d); // allow Label subclasses to pass on their Private
     Label(LabelPrivate &d); // Labelサブクラスが自身のPrivateに渡すのを許容します
     // notice how Label does not have a d_ptr! It just uses Widget's d_ptr.
     // Labelにd_ptrがなくなっていることに注意!Widgetのd_ptrを使っているだけです
};
};
</pre>
</pre>
Line 370: Line 367:


Label::Label()
Label::Label()
  : Widget(*new LabelPrivate) // initialize the d-pointer with our own Private
  : Widget(*new LabelPrivate) // 自身のdポインタを初期化
{
{
}
}
Line 394: Line 391:
void Label::setText(const String &text)
void Label::setText(const String &text)
{
{
   // won't work! since d_ptr is of type WidgetPrivate even though
   // 動作しません!d_ptrはLabelPrivateオブジェクトをポイントするものの
   // it points to LabelPrivate object
   // WidgetPrivate型ではないためです
   d_ptr->text = text;
   d_ptr->text = text;
}
}
Line 405: Line 402:
void Label::setText(const String &text)
void Label::setText(const String &text)
{
{
     LabelPrivate *d = static_cast<LabelPrivate*>(d_ptr); // cast to our private type
     LabelPrivate *d = static_cast<LabelPrivate*>(d_ptr); // 自身の非公開型にキャストします
     d->text = text;
     d->text = text;
}
}
Line 422: Line 419:


<pre>
<pre>
// With Q_D you can use the members of LabelPrivate from Label
// Q_Dを使えば、LabelPrivateのメンバーをLabelから使用できます
void Label::setText(const String &text)
void Label::setText(const String &text)
{
{
Line 429: Line 426:
}
}


// With Q_Q you can use the members of Label from LabelPrivate
// Q_Qを使えば、LabelのメンバーをLabelPrivateから使用できます
void LabelPrivate::someHelperFunction()
void LabelPrivate::someHelperFunction()
{
{
Line 466: Line 463:
</pre>
</pre>


The idea is that <tt>QLabel</tt> provides a function <tt>d_func()</tt> that allows access to its private internal class. The method itself is private (since the macro is inside a private section in qlabel.h). The <tt>d_func()</tt> can however be invoked by '''friends''' (C++ friend) of <tt>QLabel</tt>. This is primarily useful for access of information by Qt classes which cannot get access of some <tt>QLabel</tt> information using public api. As a bizarre example, <tt>QLabel</tt> might keep track of how many times the user has clicked on a link. However, there is no public API to access this information. <tt>QStatistics</tt> is a class that needs this information. A Qt developer will add <tt>QStatistics</tt> as a friend of <tt>QLabel</tt> and <tt>QStatistics</tt> can then do <tt>label->d_func()->linkClickCount</tt>.
このアイディアは、<tt>QLabel</tt>が非公開内部クラスへのアクセスを許容する<tt>d_func()</tt>関数を提供するというものです。(マクロはqlabel.hの非公開セクションにありますので)メソッド自身は非公開です。しかしながら、<tt>d_func()</tt>は、<tt>QLabel</tt>の'''フレンド'''(C++フレンド)によって呼び出すことができるのです。これは、公開APIを使って<tt>QLabel</tt>情報の一部にアクセスできないQtクラスが情報にアクセスするのに大変便利です。突飛な例として、ユーザーが何回リンクをクリックしたかを<tt>QLabel</tt>が記録しているものとしましょう。しかしながら、この情報にアクセスする公開APIはありません。<tt>QStatistics</tt>はこの情報を必要としているクラスです。Qt開発者は、<tt>QStatistics</tt><tt>QLabel</tt>のフレンドとして追加すれば、<tt>QStatistics</tt><tt>label->d_func()->linkClickCount</tt>とすることができます。


The <tt>d_func</tt> also has the advantage to enforce const-correctness: In a const member function of MyClass you need a <tt>Q_D(const MyClass)</tt> and thus you can only call const member functions in MyClassPrivate. With a ''naked'' d_ptr you could also call non-const functions.
<tt>d_func</tt>は、正しいconstを推進する上で利点があります。MyClassのconstメンバー関数では、<tt>Q_D(const MyClass)</tt>を必要とするので、MyClassPrivateのconstメンバー関数しか呼び出せません。ナマのd_ptrを使えば、非const関数を呼び出すこともできます。

Latest revision as of 02:07, 18 December 2023

En Ar Bg De El Es Fa Fi Fr Hi Hu It Ja Kn Ko Ms Nl Pl Pt Ru Sq Th Tr Uk Zh

dポインタとは

たとえば、このようなQtソースファイルを開くと、Q_DマクロやQ_Qマクロが多数ちりばめられているのを見つけることができます。この記事は、これらのマクロの目的を解説します。

Q_DおよびQ_Qマクロは、dポインタ(あるいはオペークポインタと呼ばれるデザインパターンの一部です。これによって、ライブラリの実装の細部がユーザから隠蔽され、実装の変更もバイナリ互換性を壊すことなく行うことが可能となっています。

バイナリ互換性 — それってなに?

Qtのようなライブラリをデザインする上では、Qtライブラリが他のバージョンにアップグレードされたり置き換えられたりした場合にも、Qtに動的にリンクしているアプリケーションが、再コンパイルすることなく動くことが重要です。たとえば、あなたがCuteAppというアプリをQt 4.5でビルドしたとして、Qt ライブラリがバージョン4.5から4.6にアップグレードしたとしても(Windowsではアプリとともに同梱されますが、Linuxではパッケージマネジャが自動的に導入します!)、あなたのCuteAppは依然として実行可能でなければなりません。

バイナリ互換性を壊すのはなにか?

すると、ライブラリの変更がアプリケーションの再コンパイルを必要とするのは、どんなときでしょうか。以下のような簡単な例を見てみましょう。

 class Widget
 {
 // ...
 private:
    Rect m_geometry;
 };
 
 class Label : public Widget
 {
 public:
    // ... 
    String text() const 
    {
        return m_text;
    }
 
 private:
     String m_text;
 };

ここには、ジオメトリをメンバー変数に持つウィジェットがあります。このウィジェットをコンパイルして、WidgetLib 1.0として出荷しましょう。

WidgetLib 1.1では、いいアイディアを思いついた人が居て、スタイルシートのサポートを追加したとします。なんてことは無い、新しいメソッドと新しいデータメンバを追加するだけです。

 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を出荷すると、WidgetLib 1.0でコンパイルしてうまく走っていたCuteAppが、見事にクラッシュすることになります!

どうしてクラッシュしたのか?

その理由は、新しいデータメンバを追加したことによって、ウィジェットとラベルオブジェクトのサイズを変更してしまったからです。どうしてこれが関係あるのでしょうか?それは、お使いのC++コンパイラがコードを生成する際、オブジェクト内のデータにアクセスするのに、コンパイラがオフセットを使用するからです。

上記のPODオブジェクトがメモリ内でどのように見えるか、ひじょうに簡略化した模式図を見てみましょう。

WidgetLib 1.0でのラベルオブジェクトレイアウト WidgetLib 1.1でのラベルオブジェクトレイアウト
m_geometry <オフセット0> m_geometry <オフセット0>
- - - m_stylesheet <オフセット1>
m_text <オフセット1> - - -
- - - m_text <オフセット2>

WidgetLib 1.0では、ラベルのテキストメンバは(論理的)オフセット1の場所にあります。コンパイラがアプリケーション中に生成したコードでは、Label::text()メソッドは、アプリケーション中のラベルオブジェクトから1だけオフセットした位置にアクセスするものと翻訳されるのです。しかし、WidgetLib 1.1では、ラベルのテキストメンバは(論理的)オフセット2にシフトしてしまっています!アプリケーションは、再コンパイルされていないためtextがオフセット1にあるものと考えていますので、stylesheet変数にアクセスしてしまうのです!

ここで、なぜLabel::text()のオフセット計算コードがCuteAppバイナリで終わってしまって、WidgetLibバイナリで終わらないのかと不思議に思う人がいるはずです。その答えは、Label::text()用のコードがヘッダファイルで定義されているので、コンパイラがそれをインライン化するだけで終わってしまうからです。

それでは、Label::text()がインライン化されていなければ事情は変わるのでしょうか。つまり、Label::text()をソースファイルに移動したとしたら?答えはノーです。C++コンパイラは、オブジェクトのサイズがコンパイル時と実行時で同じであることに依存しています。たとえば、スタックワインディング・アンワインディングがそうです。もしラベルオブジェクトをスタックに作成したとすると、コンパイラはスタックに領域を割り当てるのに、コンパイル時のラベルサイズを用いるコードを生成します。WidgetLib 1.1では、実行時のラベルサイズが違うために、ラベルのコンストラクタは既存のスタックデータを上書きしてしまい、スタックの破損をもたらすのです。

エクスポートされたC++クラスのサイズは決して変更してはならない

要約すると、あなたのライブラリが一旦リリースされたならば、(ユーザーに見える形で)エクスポートされたC++クラスのサイズやレイアウト(データを動かし回ってはいけません)は、けっして変更してはなりません。C++コンパイラは、クラス中のデータのサイズや順序は、アプリケーションがコンパイルされた後は変更されないと仮定してコードを生成します。

すると、どうやって、オブジェクトのサイズを変更せずに、新機能を追加できるでしょうか。

dポインタ

ここで上手い方法は、単一のポインタのみを保管することで、ライブラリのすべての公開クラスのサイズを一定に保つ方法です。このポインタは、全てのデータを含む非公開/内部データ構造体をポイントしています。この内部構造体のサイズは、アプリケーションにどんな副作用も及ぼすことなく、縮んだり大きくなったりすることができます。なぜなら、ポインタはライブラリコード内のみでアクセスされ、アプリケーションの視点からは、オブジェクトのサイズは、つねにポインタのサイズであり、けっして変化しないからです。このポインタがdポインタと呼ばれます。

このパターンの精神は、下記のコードにまとめられています(この記事のコードはすべてデストラクタを持っていませんが、もちろん実際のコードではこれらを追加しなくてはなりません)。

widget.h

/* d_ptrはポインタであり、ヘッダファイル中で参照されることは決してないので
   (その場合コンパイルエラーとなります)、WidgetPrivateをインクルードする
   必要はありませんが、代わりに前方宣言する必要があります。
   クラスの定義は、widget.cppに書くこともできますし、widget_p.hなどの
   別ファイルに書くこともできます。 */
 
 class WidgetPrivate;
 
 class Widget
 {
     // ...
     Rect geometry() const;
     // ... 
 
 private:
     WidgetPrivate *d_ptr;
 };

widget_p.h:これはウィジェットクラスの非公開ヘッダファイルです。

/* widget_p.h (_p は private を表すことにします) */
struct WidgetPrivate
{
    Rect geometry;
    String stylesheet;
};

widget.cpp

// この#includeのおかげで、WidgetPrivateにアクセスできます。
#include "widget_p.h"

Widget::Widget() : d_ptr(new WidgetPrivate)
{
    // 非公開データの生成
}

Rect Widget::geometry() const
{
    // d-ptrはライブラリコード内でのみアクセスされます
    return d_ptr->geometry;
}

つぎに、ウィジェットに基づいた子クラスの例です。

label.h

class Label : public Widget
{
    // ...
    String text();

private:
    // 各クラスは、それぞれのdポインタを保持します
    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ポインタに直接アクセスすることはありません。dポインタはWidgetLib内でのみアクセスされ、WidgetLibは各リリースで再コンパイルされるため、非公開クラスはCuteAppに影響を与えることなく、自由に変更することができます。

dポインタの他の利点

利点はバイナリ互換性に関するものばかりではありません。dポインタには、以下のように他の利点もあります。

  • 実装の詳細を隠す − WidgetLibをヘッダファイルとバイナリファイルだけで出荷することができます。.cppファイルは非公開ソースとすることができます。
  • ヘッダファイルは、実装の詳細からクリーンであり、API参照として提供することができます。
  • 実装に必要なヘッダファイルはヘッダファイルから実装(ソース)ファイルに移されるので、コンパイルがずっと速くなります。

上記の利点が自明に見えるのは、まったく以て事実です。Qtにおいてdポインタを使用する本当の理由は、バイナリ互換性にあり、Qtがクローズドソースとして始まった事実に因ります。

qポインタ

これまでは、dポインタをCスタイルのデータ構造体としてのみ見てきました。実際には、これに非公開メソッド(ヘルパー関数)が含まれます。たとえば、LabelPrivateは、マウスをクリックしたときにリンクターゲットを探すのに必要なgetLinkTargetFromPoint()というヘルパー関数を持っているかもしれません。多くの場合、これらのヘルパーメソッドは、ラベルやベースクラスのウィジェットからの関数など、公開クラスへのアクセスが必要となります。たとえば、setTextAndUpdateWidget() というヘルパーメソッドは、ウィジェットの再描画をスケジュールする公開メソッドであるWidget::update()を呼び出したいかもしれません。そこで、WidgetPrivateには公開クラスへのポインタであるqポインタを保持させるようにします。上記のコードをqポインタ用に修正すると、以下のようになります。

widget.h

class WidgetPrivate;

class Widget
{
    // ...
    Rect geometry() const;
    // ...
private:
    WidgetPrivate *d_ptr;
};

widget_p.h

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::geometry() const
{
    // d-ptrはライブラリコード中のみでアクセスされます
    return d_ptr->geometry;
}

以下は、ウィジェットに基づいた別のクラスです。

label.h

class Label : public Widget
{
    // ...
    String text() const;

private:
    LabelPrivate *d_ptr;
};

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;
}

dポインタを継承して最適化する

上記のコードにおいて、ラベルをたった一つ作るだけで、LabelPrivateWidgetPrivateへのメモリ割当てが行われてしまいました。この方針をQtで採用したとすると、QListWidgetのようなクラスでは、ひじょうに悪い状況を生み出します。このクラスは、クラス継承の階層で第6階層の深みにあるので、6つのメモリ割当てを行ってしまうのです!

この状況は、非公開クラスの継承階層を作り、dポインタによるインスタンス化された経路をたどらせることで解決します。

dポインタを継承する際には、非公開クラスの宣言は、たとえばsidget_p.hといった独立したファイルで行わなくてはならないことに注意してください。dポインタの継承を行うと、widget.cppファイルの中で宣言を行うことはできません。

widget.h

class Widget
{
public:
    Widget();
    // ...
protected:
    // サブクラスのみ以下にアクセスできます
    // サブクラスにそれら自身の具体的なPrivateを初期化するのを許容します
    Widget(WidgetPrivate &d);
    WidgetPrivate *d_ptr;
};

widget_p.h

struct WidgetPrivate
{
    WidgetPrivate(Widget *q) : q_ptr(q) { } // q-ptrを初期化するコンストラクタ
    Widget *q_ptr; // APIクラスをポイントするq-ptr
    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のd_ptrを使っているだけです
};

label.cpp

#include "widget_p.h"

class LabelPrivate : public WidgetPrivate
{
public:
    String text;
};

Label::Label()
 : Widget(*new LabelPrivate) // 自身のdポインタを初期化
{
}

Label::Label(LabelPrivate &d) : Widget(d)
{
}

この美しさがわかるでしょうか?ここでは、Labelオブジェクトを生成する際に、(WidgetPrivateの下層クラスである)LabelPrivateを作り出します。そして、LabelPrivateはウィジェットの保護されたコンストラクタへの具象化されたdポインタを渡すのです!ここではLabelオブジェクトが生成される際に、1つのメモリ割当てしか起こりません。Label自身も保護されたコンストラクタを持っているので、その下層クラスは自身の非公開クラスを提供するのにこれを用いることができます。

Qtにおけるdポインタ

Qtでは、事実上すべての公開クラスがdポインタアプローチを採用しています。これが採用されていない唯一のケースは、そのクラスにメンバー変数が追加されることが決してないことが、前もってわかっている場合です。たとえば、QPointQRectのようなクラスには、新しいメンバーが追加されることは想定されないので、データメンバーは、dポインタを用いずにクラス自体に直接保持されています。

Qtでは、すべての非公開オブジェクトは、QObjectPrivateをベースクラスとしていることに注意してください。

Q_DおよびQ_Q

上記のステップで行った最適化には、qポインタとdポインタがWidget型およびWidgetPrivate型となる副作用があります。つまり、下記のようなものは動作しません。

void Label::setText(const String &text)
{
   // 動作しません!d_ptrはLabelPrivateオブジェクトをポイントするものの
   // WidgetPrivate型ではないためです
   d_ptr->text = text;
}

したがって、dポインタに下層クラスでアクセスする場合には、適切な型へのstatic_castが必要となります。

void Label::setText(const String &text)
{
    LabelPrivate *d = static_cast<LabelPrivate*>(d_ptr); // 自身の非公開型にキャストします
    d->text = text;
}

ご覧のように、static_castをあらゆるところで見かけるのは美しくありません。代わりに以下のように、これを直感的にする2つのマクロがsrc/corelib/global/qglobal.hに定義されています。

global.h

#define Q_D(Class) Class##Private * const d = d_func()
#define Q_Q(Class) Class * const q = q_func()

label.cpp

// Q_Dを使えば、LabelPrivateのメンバーをLabelから使用できます
void Label::setText(const String &text)
{
    Q_D(Label);
    d->text = text;
}

// Q_Qを使えば、LabelのメンバーをLabelPrivateから使用できます
void LabelPrivate::someHelperFunction()
{
    Q_Q(Label);
    q->selectAll();
}

Q_DECLARE_PRIVATEおよびQ_DECLARE_PUBLIC

Qtクラスには、公開クラス用にQ_DECLARE_PRIVATEマクロがあります。このマクロは以下のようになっています。

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.h

class QLabel
{
private:
    Q_DECLARE_PRIVATE(QLabel)
};

このアイディアは、QLabelが非公開内部クラスへのアクセスを許容するd_func()関数を提供するというものです。(マクロはqlabel.hの非公開セクションにありますので)メソッド自身は非公開です。しかしながら、d_func()は、QLabelフレンド(C++フレンド)によって呼び出すことができるのです。これは、公開APIを使ってQLabel情報の一部にアクセスできないQtクラスが情報にアクセスするのに大変便利です。突飛な例として、ユーザーが何回リンクをクリックしたかをQLabelが記録しているものとしましょう。しかしながら、この情報にアクセスする公開APIはありません。QStatisticsはこの情報を必要としているクラスです。Qt開発者は、QStatisticsQLabelのフレンドとして追加すれば、QStatisticslabel->d_func()->linkClickCountとすることができます。

d_funcは、正しいconstを推進する上で利点があります。MyClassのconstメンバー関数では、Q_D(const MyClass)を必要とするので、MyClassPrivateのconstメンバー関数しか呼び出せません。ナマのd_ptrを使えば、非const関数を呼び出すこともできます。