PImplメカニズムとQtのD-Pointer実装



Pimpl Mechanism Qts D Pointer Implementation



PImplは、Pointer to Implementationの略語であり、エクスポートライブラリインターフェイスを構築する際のC ++用の独自の技術ツールです。つまり、クラスClassのすべてのプライベート変数とプライベートメソッドは、単一の実装クラスClassImplにカプセル化されます。 ClassImplへのプライベートポインタを介して、クラス内のこのプライベートデータにアクセスします。 ClassImplクラスの特定の定義と実装は、cppに入れます。 QtのD-Pointerテクノロジーは、PImplメカニズムの実装です。
利点:

The program interface has a stable ABI (application binary interface), which does not break binary compatibility. Reduces program compilation dependencies and reduces compilation time. Data hiding makes the header file very clean, does not contain implementation details, and can be used directly as an API reference.

短所:



Implementers need to do more work. This mechanism does not work for the protected method because the subclass needs access. Due to the hidden data, the code readability is somewhat reduced. Runtime performance is slightly compromised, especially when the function called is a virtual function.

バイナリ互換性の問題については、C ++バイナリ互換性の記事で詳しく説明されているため、ここでは説明しません。以下では、QtのD-Pointerの実装の詳細を紹介することにより、PImplメカニズムを理解しています。

QtでのDポインターの実装
Person.h



class PersonPrivate class Person { Q_DECLARE_PRIVATE(Person) public: Person() ~Person() QString name() const void setName(const QString &name) signals: void calcRequested() private: QScopedPointer d_ptr }

Person_p.h

#include 'Person.h' class PersonPrivate { Q_DECLARE_PUBLIC(Person) public: PersonPrivate(Person *parent) void calc() QString name private: Person * const q_ptr }

Person.cpp

#include 'Person_p.h' Person::Person() : d_ptr(new PersonPrivate(this)) { } ~Person() {} QString Person::name() const { Q_D(const Person) return d->name } void Person::setName(const QString &name) { Q_D(const Person) d->name = name } PersonPrivate::PersonPrivate(Person *parent) : q_ptr(parent) { } void PersonPrivate::calc() { Q_Q(Person) emit q->calcRequested() }

関連するマクロの定義と関数

上記で使用されているすべてのマクロ定義は、qglobal.hに配置されています。それらを一つずつ紹介していきましょう。
Q_DECLARE_PRIVATE、Q_D

template static inline T *qGetPtrHelper(T *ptr) { return ptr } template 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(qGetPtrHelper(d_ptr)) } inline const Class##Private* d_func() const { return reinterpret_cast(qGetPtrHelper(d_ptr)) } friend class Class##Private #define Q_D(Class) Class##Private * const d = d_func()

Q_DECLARE_PRIVATEは複雑に見えますが、実際にはd_func()関数をカプセル化しているため、さまざまな状況でプライベートクラスポインターd_ptrを簡単に取得して使用できます。 Q_Dマクロはd_func()を再パッケージ化するため、各定義の面倒な作業を回避し、dポインターを直接使用できます。このポインタは、必要なd_ptrです。以下に、より詳細な理解と関連する注記があります。

Using the d_func() function, we can avoid the type conversion directly every time we take the d_ptr pointer (because we might use this method in a subclass, as we will see later in the extension). In d_func(), why don't we use d_ptr directly, but with the qGetPtrHelper() function? This function is used to adapt to the situation where we use smart pointers, because at this point we need to get the real pointer, we need to call d_ptr.data(). Using Q_D in the const function, the const version of d_func() will be called. We must use the Q_D (const Person) method to get the correct const pointer (otherwise it will indicate that it cannot be converted). This package also indirectly guarantees the correctness of the program, rather than directly taking the d_ptr pointer to operate. The definition of d_ptr is to be placed in the header file exposed to the user. Such a name sometimes breaks our naming convention. In this case, the Q_DECLARE_PRIVATE_D(m_dPtr, Person) macro can be used for custom naming. Seeing this macro, we have to feel the subtlety of the Qt package. Since the above mentioned the use of smart pointers, here are a few more words, we use the pre-declaration method to use QScopedPointer, we must have non-inline construct, destructor, assignment operator. That is, it cannot be generated by default. See the Forward Declared Pointers section of the QScopedPointer documentation for details.

Q_DECLARE_PUBLIC、Q_Q

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

同様に、プライベートクラスでは、メインクラスのメソッドを呼び出す必要がある場合があります。これら2つのマクロの目的は、プライベートクラスのメインクラスのポインターを取得することです。プライベートクラスのコンストラクターでメインクラスポインターを渡し、それをq_ptrに割り当てます。これはメインクラスへのポインターであるため、スマートポインターに問題はなく、qGetPtrHelper()関数はここでは使用されません。
拡張

以上の説明で、ここで問題を考えることができます。クラスに多くのサブクラスがある場合、サブクラスごとにd_ptrを定義する必要はありません。すべてのプライベートクラスにはq_ptrへのポインタが必要ですか?もちろん、これはQtには当てはまらないので、次の最適化されたバージョンがあります。

まず、d_ptrを基本クラスQObjectの保護された型に変更し、保護された型コンストラクターを基本クラスに追加して、サブクラスで使用できるようにします。

class QObject { protected: QObject(QObjectPrivate &dd, QObject *parent = 0) QScopedPointer d_ptr ... }

すべてのプライベートクラスはQObjectPrivateから継承します。

class QWidgetPrivate : public QObjectPrivate { Q_OBJECT Q_DECLARE_PUBLIC(QWidget) ... }

QWidgetとQObjectのコンストラクターを見てみましょう。

QWidget::QWidget(QWidget *parent, Qt::WindowFlags f) : QObject(*new QWidgetPrivate, 0), QPaintDevice() { ... } 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 }

ここで、真実がついに明らかになりました。QWidgetには、元々Qbjectから継承されたd_ptrポインターはありません。 QObjectに新しく追加された保護されたコンストラクターはQWidgetPrivateに渡されます。これは、QObjectのd_ptrに値を割り当てるために使用されます。これが唯一のd_ptrです。最後に、d_func()でのこれらの型変換の役割を理解します。これは、正しい型の現在のプライベートポインターを確実に取得できるようにするためです。

したがって、同じ理由で、ObjectPrivateはQObjectDataから継承され、QObject * q_ptrはQObjectDataから継承されます。 QObjectサブクラスのすべてのプライベートクラスはObjectPrivateを継承するため、q_ptrはサブクラスに表示されません。 QObjectのコンストラクターで、thisポインターをQObjectに割り当てます。 Q_Qマクロを使用することで、同じことができます。タイプのメインクラスqポインタ。
概要

Qtマクロを使用せずにPImplを実装できます。実際、プライベートクラスを作成し、それをcppに入れて、すでにPImplを実装するだけで済みます。ただし、これらのマクロを使用すると、Qtスタイルのデータ隠蔽を簡単に実装できます。上記のPersonクラスの簡略版を使用できます。もちろん、クラスを継承する必要がある場合は、ObjectPrivateクラスの継承を使用して、拡張メソッドを参照することもできます。このクラスを使用するには、達成する方法ですが、注意を払う必要があります。プロにQT + = core-privateを追加する必要があります。

参照:Dポイント
https://wiki.qt.io/D-Pointer/zh