久久久久久久av_日韩在线中文_看一级毛片视频_日本精品二区_成人深夜福利视频_武道仙尊动漫在线观看

如何使用 Qt 的 PIMPL 成語?

How to use the Qt#39;s PIMPL idiom?(如何使用 Qt 的 PIMPL 成語?)
本文介紹了如何使用 Qt 的 PIMPL 成語?的處理方法,對(duì)大家解決問題具有一定的參考價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)吧!

問題描述

PIMPL 代表 Pointer 到 IMPLmentation.實(shí)現(xiàn)代表實(shí)現(xiàn)細(xì)節(jié)":類的用戶不需要關(guān)心的東西.

Qt 自己的類實(shí)現(xiàn)通過使用 PIMPL 習(xí)語將接口與實(shí)現(xiàn)清晰地分開.然而,Qt 提供的機(jī)制沒有記錄.如何使用它們?

我希望這是 Qt 中關(guān)于我如何 PIMPL"的規(guī)范問題.答案是由一個(gè)簡單的坐標(biāo)輸入對(duì)話框界面激發(fā)的,如下所示.

當(dāng)我們有一個(gè)半復(fù)雜的實(shí)現(xiàn)時(shí),使用 PIMPL 的動(dòng)機(jī)就變得很明顯了..

  • PIMPL 的陷阱和陷阱.
  • 界面

    我們現(xiàn)在將在問題中解釋基于 PIMPL 的 CoordinateDialog 接口.

    Qt 提供了幾個(gè)宏和實(shí)現(xiàn)助手,可以減少 PIMPL 的苦差事.實(shí)現(xiàn)要求我們遵循以下規(guī)則:

    • Foo 類的 PIMPL 被命名為 FooPrivate.
    • PIMPL 在接口(頭文件)文件中與 Foo 類的聲明一起前向聲明.

    Q_DECLARE_PRIVATE 宏

    Q_DECLARE_PRIVATE 宏必須放在類聲明的 private 部分.它將接口類的名稱作為參數(shù).它聲明了 d_func() 輔助方法的兩個(gè)內(nèi)聯(lián)實(shí)現(xiàn).該方法返回具有適當(dāng)常量的 PIMPL 指針.在 const 方法中使用時(shí),它返回一個(gè)指向 const PIMPL 的指針.在非常量方法中,它返回一個(gè)指向非常量 PIMPL 的指針.它還在派生類中提供了正確類型的 pimpl.因此,從實(shí)現(xiàn)內(nèi)部對(duì) pimpl 的所有訪問都將使用 d_func() 和 ** 而不是通過 d_ptr 完成.通常我們會(huì)使用 Q_D 宏,如下面的實(shí)現(xiàn)部分所述.

    宏有兩種形式:

    Q_DECLARE_PRIVATE(Class)//假設(shè) PIMPL 指針命名為 d_ptrQ_DECLARE_PRIVATE_D(Dptr, Class)//顯式采用 PIMPL 指針名稱

    在我們的例子中,Q_DECLARE_PRIVATE(CoordinateDialog) 等價(jià)于 Q_DECLARE_PRIVATE_D(d_ptr, CoordinateDialog).

    Q_PRIVATE_SLOT 宏

    此宏僅在與 Qt 4 兼容或面向非 C++11 編譯器時(shí)才需要.對(duì)于 Qt 5、C++11 代碼,這是不必要的,因?yàn)槲覀兛梢詫⒑舆B接到信號(hào)并且不需要顯式私有槽.

    我們有時(shí)需要 QObject 來擁有供內(nèi)部使用的私有插槽.這樣的插槽會(huì)污染接口的私有部分.由于插槽信息僅與 moc 代碼生成器相關(guān),因此我們可以使用 Q_PRIVATE_SLOT 宏來告訴 moc 通過 d_func() 指針,而不是通過 this.

    Q_PRIVATE_SLOT 中 moc 預(yù)期的語法是:

    Q_PRIVATE_SLOT(instance_pointer, 方法簽名)

    在我們的例子中:

    Q_PRIVATE_SLOT(d_func(), void onAccepted())

    這有效地聲明了 CoordinateDialog 類上的 onAccepted 槽.moc 生成以下代碼來調(diào)用插槽:

    d_func()->onAccepted()

    宏本身有一個(gè)空擴(kuò)展 - 它只向 moc 提供信息.

    我們的接口類因此擴(kuò)展如下:

    class CoordinateDialog : public QDialog{Q_OBJECT/* 我們不在這里展開它,因?yàn)樗穷}外話.*///Q_DECLARE_PRIVATE(CoordinateDialog)內(nèi)聯(lián) CoordinateDialogPrivate* d_func() {return reinterpret_cast(qGetPtrHelper(d_ptr));}內(nèi)聯(lián) const CoordinateDialogPrivate* d_func() const {return reinterpret_cast(qGetPtrHelper(d_ptr));}朋友類 CoordinateDialogPrivate;//Q_PRIVATE_SLOT(d_func(), void onAccepted())//(空的)QScopedPointerconst d_ptr;民眾:[...]};

    使用這個(gè)宏時(shí),必須在私有類完全定義的地方包含moc生成的代碼.在我們的例子中,這意味著 CoordinateDialog.cpp 文件應(yīng)該結(jié)束:

    #include "moc_CoordinateDialog.cpp";

    問題

    • 要在類聲明中使用的所有 Q_ 宏都已包含分號(hào).Q_ 后不需要明確的分號(hào):

      //正確//冗長,有雙分號(hào)類 Foo : 公共 QObject { 類 Foo : 公共 QObject {Q_OBJECT Q_OBJECT;Q_DECLARE_PRIVATE(...) Q_DECLARE_PRIVATE(...);……};};

    • PIMPL 不得Foo 自身內(nèi)的私有類:

      //正確//錯(cuò)誤類 FooPrivate;類 Foo {類 Foo { 類 FooPrivate;……};};

    • 默認(rèn)情況下,類聲明中左大括號(hào)之后的第一部分是私有的.因此以下是等價(jià)的:

      //少啰嗦,首選//冗長類 Foo { 類 Foo {內(nèi)部私人會(huì)員;私人的:內(nèi)部私人會(huì)員;};};

    • Q_DECLARE_PRIVATE 需要接口類的名稱,而不是 PIMPL 的名稱:

      //正確//錯(cuò)誤類 Foo { 類 Foo {Q_DECLARE_PRIVATE(Foo) Q_DECLARE_PRIVATE(FooPrivate)……};};

    • 對(duì)于不可復(fù)制/不可分配的類,例如 QObject,PIMPL 指針應(yīng)該是常量.在實(shí)現(xiàn)可復(fù)制類時(shí),它可以是非常量.

    • 由于 PIMPL 是一個(gè)內(nèi)部實(shí)現(xiàn)細(xì)節(jié),它的大小在使用該接口的站點(diǎn)上是不可用的.應(yīng)該抵制使用placement new 和Fast Pimpl 習(xí)語的誘惑,因?yàn)樗鼪]有任何好處除了一個(gè)根本不分配內(nèi)存的類.

    實(shí)施

    PIMPL 必須在實(shí)現(xiàn)文件中定義.如果它很大,也可以定義在一個(gè)私有頭文件中,對(duì)于接口在foo.h中的類,通常命名為foo_p.h.

    PIMPL 至少只是主類數(shù)據(jù)的載體.它只需要一個(gè)構(gòu)造函數(shù),不需要其他方法.在我們的例子中,它還需要存儲(chǔ)指向主類的指針,因?yàn)槲覀兿M麖闹黝惏l(fā)出信號(hào).因此:

    //CordinateDialog.cpp#include #include #include 類 CoordinateDialogPrivate {Q_DISABLE_COPY(CoordinateDialogPrivate)Q_DECLARE_PUBLIC(坐標(biāo)對(duì)話框)CoordinateDialog * const q_ptr;QFormLayout布局;QDoubleSpinBox x, y, z;QDialogBu??ttonBox 按鈕;QVector3D 坐標(biāo);無效 onAccepted();CoordinateDialogPrivate(CoordinateDialog*);};

    PIMPL 不可復(fù)制.由于我們使用不可復(fù)制的成員,任何復(fù)制或分配給 PIMPL 的嘗試都會(huì)被編譯器捕獲.通常,最好使用 Q_DISABLE_COPY 顯式禁用復(fù)制功能.

    Q_DECLARE_PUBLIC 宏的工作方式與 Q_DECLARE_PRIVATE 類似.本節(jié)稍后將對(duì)其進(jìn)行描述.

    我們將指向?qū)υ捒虻闹羔槀鬟f給構(gòu)造函數(shù),允許我們?cè)趯?duì)話框上初始化布局.我們還將 QDialog 的接受信號(hào)連接到內(nèi)部 onAccepted 槽.

    CoordinateDialogPrivate::CoordinateDialogPrivate(CoordinateDialog * dialog) :q_ptr(對(duì)話框),布局(對(duì)話框),按鈕(QDialogBu??ttonBox::Ok | QDialogBu??ttonBox::Cancel){layout.addRow(X", &x);layout.addRow(Y", &y);layout.addRow(Z", &z);layout.addRow(&buttons);dialog->connect(&buttons, SIGNAL(accepted()), SLOT(accept()));dialog->connect(&buttons, SIGNAL(rejected()), SLOT(reject()));#if QT_VERSION <= QT_VERSION_CHECK(5,0,0)this->connect(dialog, SIGNAL(accepted()), SLOT(onAccepted()));#別的QObject::connect(dialog, &QDialog::accepted, [this]{ onAccepted(); });#萬一}

    onAccepted() PIMPL 方法需要作為 Qt 4/非 C++11 項(xiàng)目中的插槽公開.對(duì)于 Qt 5 和 C++11,這不再是必要的.

    在接受對(duì)話后,我們捕獲坐標(biāo)并發(fā)出acceptedCoordinates 信號(hào).這就是為什么我們需要公共指針:

    void CoordinateDialogPrivate::onAccepted() {Q_Q(坐標(biāo)對(duì)話框);坐標(biāo).setX(x.value());坐標(biāo).setY(y.value());坐標(biāo).setZ(z.value());發(fā)出 q->acceptedCoordinates(coordinates);}

    Q_Q 宏聲明了一個(gè)本地 CoordinateDialog * const q 變量.本節(jié)稍后將對(duì)其進(jìn)行描述.

    實(shí)現(xiàn)的公共部分構(gòu)造 PIMPL 并公開其屬性:

    CoordinateDialog::CoordinateDialog(QWidget * parent, Qt::WindowFlags flags) :QDialog(父,標(biāo)志),d_ptr(新的 CoordinateDialogPrivate(this)){}QVector3D CoordinateDialog::coordinates() const {Q_D(const CoordinateDialog);返回 d-> 坐標(biāo);}CoordinateDialog::~CoordinateDialog() {}

    Q_D 宏聲明了一個(gè)本地 CoordinateDialogPrivate * const d 變量.描述如下.

    Q_D 宏

    要在 interface 方法中訪問 PIMPL,我們可以使用 Q_D 宏,將接口類的名稱傳遞給它.

    void Class::foo()/* 非常量 */{Q_D(類);/* 需要一個(gè)分號(hào)!*///擴(kuò)展為ClassPrivate * const d = d_func();...

    要在 const interface 方法中訪問 PIMPL,我們需要在類名前加上 const 關(guān)鍵字:

    void Class::bar() const {Q_D(const 類);//擴(kuò)展為const ClassPrivate * const d = d_func();...

    Q_Q 宏

    要從非常量 PIMPL 方法訪問接口實(shí)例,我們可以使用 Q_Q 宏,將接口類的名稱傳遞給它.

    void ClassPrivate::foo()/* 非常量*/{Q_Q(班級(jí));/* 需要一個(gè)分號(hào)!*///擴(kuò)展為類 * const q = q_func();...

    為了在 const PIMPL 方法中訪問接口實(shí)例,我們?cè)陬惷凹由?const 關(guān)鍵字,就像我們對(duì) Q_D宏:

    void ClassPrivate::foo() const {Q_Q(const 類);/* 需要一個(gè)分號(hào)!*///擴(kuò)展為const 類 * const q = q_func();...

    Q_DECLARE_PUBLIC 宏

    這個(gè)宏是可選的,用于允許從 PIMPL 訪問接口.如果 PIMPL 的方法需要操作接口的基類或發(fā)出其信號(hào),則通常使用它.等效的 Q_DECLARE_PRIVATE 宏用于允許從界面訪問 PIMPL.

    宏將接口類的名稱作為參數(shù).它聲明了 q_func() 輔助方法的兩個(gè)內(nèi)聯(lián)實(shí)現(xiàn).該方法返回具有適當(dāng)常量的接口指針.在 const 方法中使用時(shí),它返回一個(gè)指向 const 接口的指針.在非常量方法中,它返回一個(gè)指向非常量接口的指針.它還在派生類中提供正確類型的接口.因此,從 PIMPL 內(nèi)部對(duì)接口的所有訪問都將使用 q_func() 和 ** 而不是通過 q_ptr 完成.通常我們會(huì)使用 Q_Q 宏,如上所述.

    宏期望指向接口的指針命名為q_ptr.這個(gè)宏沒有允許為接口指針選擇不同名稱的兩個(gè)參數(shù)變體(如 Q_DECLARE_PRIVATE 的情況).

    宏展開如下:

    class CoordinateDialogPrivate {//Q_DECLARE_PUBLIC(CoordinateDialog)內(nèi)聯(lián) CoordinateDialog* q_func() {return static_cast(q_ptr);}內(nèi)聯(lián) const CoordinateDialog* q_func() const {return static_cast(q_ptr);}朋友類 CoordinateDialog;//CoordinateDialog * const q_ptr;...};

    Q_DISABLE_COPY 宏

    這個(gè)宏刪除了復(fù)制構(gòu)造函數(shù)和賦值運(yùn)算符.它必須出現(xiàn)在 PIMPL 的私有部分.

    常見問題

    • 給定類的 interface 標(biāo)頭必須是要包含在實(shí)現(xiàn)文件中的第一個(gè)標(biāo)頭.這強(qiáng)制標(biāo)頭是自包含的,而不依賴于碰巧包含在實(shí)現(xiàn)中的聲明.如果不是這樣,實(shí)現(xiàn)將無法編譯,允許您修復(fù)接口以使其自給自足.

      //正確//容易出錯(cuò)//Foo.cpp//Foo.cpp#include Foo.h"#include <SomethingElse>#include <SomethingElse>#include Foo.h"http://現(xiàn)在是Foo.h"可以不依賴其他東西//我們意識(shí)到這個(gè)事實(shí).

    • Q_DISABLE_COPY 宏必須出現(xiàn)在 PIMPL 的私有部分

      //正確//錯(cuò)誤//Foo.cpp//Foo.cpp類 FooPrivate { 類 FooPrivate {Q_DISABLE_COPY(FooPrivate) 公開:... Q_DISABLE_COPY(FooPrivate)};...};

    PIMPL 和非 QObject 可復(fù)制類

    PIMPL 習(xí)慣用法允許實(shí)現(xiàn)可復(fù)制、可復(fù)制和可移動(dòng)構(gòu)造、可分配的對(duì)象.分配是通過 copy-and-swap 成語完成的,防止代碼重復(fù).PIMPL 指針當(dāng)然不能是常量.

    在 C++11 中,我們需要注意四規(guī)則,并提供所有 以下內(nèi)容:復(fù)制構(gòu)造函數(shù)、移動(dòng)構(gòu)造函數(shù)、賦值運(yùn)算符和析構(gòu)函數(shù).還有獨(dú)立的 swap 函數(shù)來實(shí)現(xiàn)這一切,當(dāng)然?.

    我們將使用一個(gè)相當(dāng)無用但仍然正確的例子來說明這一點(diǎn).

    界面

    //Integer.h#include <算法>#include 類IntegerPrivate;類整數(shù){Q_DECLARE_PRIVATE(整數(shù))QScopedPointerd_ptr;民眾:整數(shù)();整數(shù)(int);整數(shù)(常量整數(shù)和其他);整數(shù)(整數(shù)&&其他);運(yùn)算符 int&();運(yùn)算符 int() const;整數(shù) &運(yùn)算符=(整數(shù)其他);朋友無效交換(整數(shù)和第一,整數(shù)和第二)/* nothrow */;~整數(shù)();};

    為了性能,移動(dòng)構(gòu)造函數(shù)和賦值運(yùn)算符應(yīng)該在接口(頭)文件中定義.他們不需要直接訪問 PIMPL:

    Integer::Integer(Integer && other) : Integer() {交換(*這個(gè),其他);}整數(shù) &整數(shù)::運(yùn)算符=(整數(shù)其他){交換(*這個(gè),其他);返回 *this;}

    所有這些都使用 swap 獨(dú)立函數(shù),我們也必須在接口中定義它.注意是

    void swap(Integer& first, Integer& second)/* nothrow */{使用 std::swap;交換(第一個(gè).d_ptr,第二個(gè).d_ptr);}

    實(shí)施

    這很簡單.我們不需要從 PIMPL 訪問接口,因此 Q_DECLARE_PUBLICq_ptr 不存在.

    //Integer.cpp#include Integer.h"類整數(shù)私有{民眾:整數(shù)值;IntegerPrivate(int i) : value(i) {}};Integer::Integer() : d_ptr(new IntegerPrivate(0)) {}Integer::Integer(int i) : d_ptr(new IntegerPrivate(i)) {}Integer::Integer(const Integer &other) :d_ptr(new IntegerPrivate(other.d_func()->value)) {}Integer::operator int&() { return d_func()->value;}Integer::operator int() const { return d_func()->value;}整數(shù)::~整數(shù)() {}

    ?Per 這個(gè)很好的答案:還有其他說法我們應(yīng)該為我們的類型專門化 std::swap,提供一個(gè)類內(nèi) swap 以及一個(gè)自由函數(shù) swap,等等.但是這都是不必要的:任何 swap 的正確使用都將通過不合格的調(diào)用,我們的函數(shù)將通過 ADL.一個(gè)功能就行.

    PIMPL stands for Pointer to IMPLementation. The implementation stands for "implementation detail": something that the users of the class need not to be concerned with.

    Qt's own class implementations cleanly separate out the interfaces from the implementations through the use of the PIMPL idiom. Yet, the mechanisms provided by Qt are undocumented. How to use them?

    I'd like this to be the canonical question about "how do I PIMPL" in Qt. The answers are to be motivated by a simple coordinate entry dialog interface shown below.

    The motivation for the use of PIMPL becomes apparent when we have anything with a semi-complex implementation. Further motivation is given in this question. Even a fairly simple class has to pull in a lot of other headers in its interface.

    The PIMPL-based interface is fairly clean and readable.

    // CoordinateDialog.h
    #include <QDialog>
    #include <QVector3D>
    
    class CoordinateDialogPrivate;
    class CoordinateDialog : public QDialog
    {
      Q_OBJECT
      Q_DECLARE_PRIVATE(CoordinateDialog)
    #if QT_VERSION <= QT_VERSION_CHECK(5,0,0)
      Q_PRIVATE_SLOT(d_func(), void onAccepted())
    #endif
      QScopedPointer<CoordinateDialogPrivate> const d_ptr;
    public:
      CoordinateDialog(QWidget * parent = 0, Qt::WindowFlags flags = 0);
      ~CoordinateDialog();
      QVector3D coordinates() const;
      Q_SIGNAL void acceptedCoordinates(const QVector3D &);
    };
    Q_DECLARE_METATYPE(QVector3D)
    

    A Qt 5, C++11 based interface doesn't need the Q_PRIVATE_SLOT line.

    Compare that to a non-PIMPL interface that tucks implementation details into the private section of the interface. Note how much other code has to be included.

    // CoordinateDialog.h
    #include <QDialog>
    #include <QVector3D>
    #include <QFormLayout>
    #include <QDoubleSpinBox>
    #include <QDialogButtonBox>
    
    class CoordinateDialog : public QDialog
    {
      QFormLayout m_layout;
      QDoubleSpinBox m_x, m_y, m_z;
      QVector3D m_coordinates;
      QDialogButtonBox m_buttons;
      Q_SLOT void onAccepted();
    public:
      CoordinateDialog(QWidget * parent = 0, Qt::WindowFlags flags = 0);
      QVector3D coordinates() const;
      Q_SIGNAL void acceptedCoordinates(const QVector3D &);
    };
    Q_DECLARE_METATYPE(QVector3D)
    

    Those two interfaces are exactly equivalent as far as their public interface is concerned. They have the same signals, slots and public methods.

    解決方案

    Introduction

    The PIMPL is a private class that contains all of the implementation-specific data of the parent class. Qt provides a PIMPL framework and a set of conventions that need to be followed when using that framework. Qt's PIMPLs can be used in all classes, even those not derived from QObject.

    The PIMPL needs to be allocated on the heap. In idiomatic C++, we must not manage such storage manually, but use a smart pointer. Either QScopedPointer or std::unique_ptr work for this purpose. Thus, a minimal pimpl-based interface, not derived from QObject, might look like:

    // Foo.h
    #include <QScopedPointer>
    class FooPrivate; ///< The PIMPL class for Foo
    class Foo {
      QScopedPointer<FooPrivate> const d_ptr;
    public:
      Foo();
      ~Foo();
    };
    

    The destructor's declaration is necessary, since the scoped pointer's destructor needs to destruct an instance of the PIMPL. The destructor must be generated in the implementation file, where the FooPrivate class lives:

    // Foo.cpp
    class FooPrivate { };
    Foo::Foo() : d_ptr(new FooPrivate) {}
    Foo::~Foo() {}
    

    See also:

    • A deeper exposition of the idiom.
    • Gotchas and pitfalls of PIMPL.

    The Interface

    We'll now explain the PIMPL-based CoordinateDialog interface in the question.

    Qt provides several macros and implementation helpers that reduce the drudgery of PIMPLs. The implementation expects us to follow these rules:

    • The PIMPL for a class Foo is named FooPrivate.
    • The PIMPL is forward-declared along the declaration of the Foo class in the interface (header) file.

    The Q_DECLARE_PRIVATE Macro

    The Q_DECLARE_PRIVATE macro must be put in the private section of the class's declaration. It takes the interface class's name as a parameter. It declares two inline implementations of the d_func() helper method. That method returns the PIMPL pointer with proper constness. When used in const methods, it returns a pointer to a const PIMPL. In non-const methods, it returns a pointer to a non-const PIMPL. It also provides a pimpl of correct type in derived classes. It follows that all access to the pimpl from within the implementation is to be done using d_func() and **not through d_ptr. Usually we'd use the Q_D macro, described in the Implementation section below.

    The macro comes in two flavors:

    Q_DECLARE_PRIVATE(Class)   // assumes that the PIMPL pointer is named d_ptr
    Q_DECLARE_PRIVATE_D(Dptr, Class) // takes the PIMPL pointer name explicitly
    

    In our case, Q_DECLARE_PRIVATE(CoordinateDialog) is equivalent to Q_DECLARE_PRIVATE_D(d_ptr, CoordinateDialog).

    The Q_PRIVATE_SLOT Macro

    This macro is only needed for Qt 4 compatibility, or when targeting non-C++11 compilers. For Qt 5, C++11 code, it is unnecessary, as we can connect functors to signals and there's no need for explicit private slots.

    We sometimes need for a QObject to have private slots for internal use. Such slots would pollute the interface's private section. Since the information about slots is only relevant to the moc code generator, we can, instead, use the Q_PRIVATE_SLOT macro to tell moc that a given slot is to be invoked through the d_func() pointer, instead of through this.

    The syntax expected by moc in the Q_PRIVATE_SLOT is:

    Q_PRIVATE_SLOT(instance_pointer, method signature)
    

    In our case:

    Q_PRIVATE_SLOT(d_func(), void onAccepted())
    

    This effectively declares an onAccepted slot on the CoordinateDialog class. The moc generates the following code to invoke the slot:

    d_func()->onAccepted()
    

    The macro itself has an empty expansion - it only provides information to moc.

    Our interface class is thus expanded as follows:

    class CoordinateDialog : public QDialog
    {
      Q_OBJECT /* We don't expand it here as it's off-topic. */
      // Q_DECLARE_PRIVATE(CoordinateDialog)
      inline CoordinateDialogPrivate* d_func() { 
        return reinterpret_cast<CoordinateDialogPrivate *>(qGetPtrHelper(d_ptr));
      }
      inline const CoordinateDialogPrivate* d_func() const { 
        return reinterpret_cast<const CoordinateDialogPrivate *>(qGetPtrHelper(d_ptr));
      }
      friend class CoordinateDialogPrivate;
      // Q_PRIVATE_SLOT(d_func(), void onAccepted())
      // (empty)
      QScopedPointer<CoordinateDialogPrivate> const d_ptr;
    public:
      [...]
    };
    

    When using this macro, you must include the moc-generated code in a place where the private class is fully defined. In our case, this means that the CoordinateDialog.cpp file should end with:

    #include "moc_CoordinateDialog.cpp"
    

    Gotchas

    • All of the Q_ macros that are to be used in a class declaration already include a semicolon. No explicit semicolons are needed after Q_:

        // correct                       // verbose, has double semicolons
        class Foo : public QObject {     class Foo : public QObject {
          Q_OBJECT                         Q_OBJECT;
          Q_DECLARE_PRIVATE(...)           Q_DECLARE_PRIVATE(...);
          ...                              ...
        };                               };
      

    • The PIMPL must not be a private class within Foo itself:

        // correct                  // wrong
        class FooPrivate;           class Foo {
        class Foo {                   class FooPrivate;
          ...                         ...
        };                          };
      

    • The first section after the opening brace in a class declaration is private by default. Thus the following are equivalent:

        // less wordy, preferred    // verbose
        class Foo {                 class Foo {              
          int privateMember;        private:
                                      int privateMember;
        };                          };
      

    • The Q_DECLARE_PRIVATE expects the interface class's name, not the PIMPL's name:

        // correct                  // wrong
        class Foo {                 class Foo {
          Q_DECLARE_PRIVATE(Foo)      Q_DECLARE_PRIVATE(FooPrivate)
          ...                         ...
        };                          };
      

    • The PIMPL pointer should be const for non-copyable/non-assignable classes such as QObject. It can be non-const when implementing copyable classes.

    • Since the PIMPL is an internal implementation detail, its size is not available at the site where the interface is used. The temptation to use placement new and the Fast Pimpl idiom should be resisted as it provides no benefits for anything but a class that doesn't allocate memory at all.

    The Implementation

    The PIMPL has to be defined in the implementation file. If it is large, it can also be defined in a private header, customarily named foo_p.h for a class whose interface is in foo.h.

    The PIMPL, at a minimum, is merely a carrier of the main class's data. It only needs a constructor and no other methods. In our case, it also needs to store the pointer to the main class, as we'll want to emit a signal from the main class. Thus:

    // CordinateDialog.cpp
    #include <QFormLayout>
    #include <QDoubleSpinBox>
    #include <QDialogButtonBox>
    
    class CoordinateDialogPrivate {
      Q_DISABLE_COPY(CoordinateDialogPrivate)
      Q_DECLARE_PUBLIC(CoordinateDialog)
      CoordinateDialog * const q_ptr;
      QFormLayout layout;
      QDoubleSpinBox x, y, z;
      QDialogButtonBox buttons;
      QVector3D coordinates;
      void onAccepted();
      CoordinateDialogPrivate(CoordinateDialog*);
    };
    

    The PIMPL is not copyable. Since we use non-copyable members, any attempt to copy or assign to the PIMPL would be caught by the compiler. Generally, it's best to explicitly disable the copy functionality by using Q_DISABLE_COPY.

    The Q_DECLARE_PUBLIC macro works similarly to Q_DECLARE_PRIVATE. It is described later in this section.

    We pass the pointer to the dialog into the constructor, allowing us to initialize the layout on the dialog. We also connect the QDialog's accepted signal to the internal onAccepted slot.

    CoordinateDialogPrivate::CoordinateDialogPrivate(CoordinateDialog * dialog) :
      q_ptr(dialog),
      layout(dialog),
      buttons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel)
    {
      layout.addRow("X", &x);
      layout.addRow("Y", &y);
      layout.addRow("Z", &z);
      layout.addRow(&buttons);
      dialog->connect(&buttons, SIGNAL(accepted()), SLOT(accept()));
      dialog->connect(&buttons, SIGNAL(rejected()), SLOT(reject()));
    #if QT_VERSION <= QT_VERSION_CHECK(5,0,0)
      this->connect(dialog, SIGNAL(accepted()), SLOT(onAccepted()));
    #else
      QObject::connect(dialog, &QDialog::accepted, [this]{ onAccepted(); });
    #endif
    }
    

    The onAccepted() PIMPL method needs to be exposed as a slot in Qt 4/non-C++11 projects. For Qt 5 and C++11, this is no longer necessary.

    Upon the acceptance of the dialog, we capture the coordinates and emit the acceptedCoordinates signal. That's why we need the public pointer:

    void CoordinateDialogPrivate::onAccepted() {
      Q_Q(CoordinateDialog);
      coordinates.setX(x.value());
      coordinates.setY(y.value());
      coordinates.setZ(z.value());
      emit q->acceptedCoordinates(coordinates);
    }
    

    The Q_Q macro declares a local CoordinateDialog * const q variable. It is described later in this section.

    The public part of the implementation constructs the PIMPL and exposes its properties:

    CoordinateDialog::CoordinateDialog(QWidget * parent, Qt::WindowFlags flags) :
      QDialog(parent, flags),
      d_ptr(new CoordinateDialogPrivate(this))
    {}
    
    QVector3D CoordinateDialog::coordinates() const {
      Q_D(const CoordinateDialog);
      return d->coordinates;
    }
    
    CoordinateDialog::~CoordinateDialog() {}
    

    The Q_D macro declares a local CoordinateDialogPrivate * const d variable. It is described below.

    The Q_D Macro

    To access the PIMPL in an interface method, we can use the Q_D macro, passing it the name of the interface class.

    void Class::foo() /* non-const */ {
      Q_D(Class);    /* needs a semicolon! */
      // expands to
      ClassPrivate * const d = d_func();
      ...
    

    To access the PIMPL in a const interface method, we need to prepend the class name with the const keyword:

    void Class::bar() const {
      Q_D(const Class);
      // expands to
      const ClassPrivate * const d = d_func();
      ...
    

    The Q_Q Macro

    To access the interface instance from a non-const PIMPL method, we can use the Q_Q macro, passing it the name of the interface class.

    void ClassPrivate::foo() /* non-const*/ {
      Q_Q(Class);   /* needs a semicolon! */
      // expands to
      Class * const q = q_func();
      ...
    

    To access the interface instance in a const PIMPL method, we prepend the class name with the const keyword, just as we did for the Q_D macro:

    void ClassPrivate::foo() const {
      Q_Q(const Class);   /* needs a semicolon! */
      // expands to
      const Class * const q = q_func();
      ...
    

    The Q_DECLARE_PUBLIC Macro

    This macro is optional and is used to allow access to the interface from the PIMPL. It is typically used if the PIMPL's methods need to manipulate the interface's base class, or emit its signals. The equivalent Q_DECLARE_PRIVATE macro was used to allow access to the PIMPL from the interface.

    The macro takes the interface class's name as a parameter. It declares two inline implementations of the q_func() helper method. That method returns the interface pointer with proper constness. When used in const methods, it returns a pointer to a const interface. In non-const methods, it returns a pointer to a non-const interface. It also provides the interface of correct type in derived classes. It follows that all access to the interface from within the PIMPL is to be done using q_func() and **not through q_ptr. Usually we'd use the Q_Q macro, described above.

    The macro expects the pointer to the interface to be named q_ptr. There is no two-argument variant of this macro that would allow to choose a different name for the interface pointer (as was the case for Q_DECLARE_PRIVATE).

    The macro expands as follows:

    class CoordinateDialogPrivate {
      //Q_DECLARE_PUBLIC(CoordinateDialog)
      inline CoordinateDialog* q_func() {
        return static_cast<CoordinateDialog*>(q_ptr);
      }
      inline const CoordinateDialog* q_func() const {
        return static_cast<const CoordinateDialog*>(q_ptr);
      }
      friend class CoordinateDialog;
      //
      CoordinateDialog * const q_ptr;
      ...
    };
    

    The Q_DISABLE_COPY Macro

    This macro deletes the copy constructor and the assignment operator. It must appear in the private section of the PIMPL.

    Common Gotchas

    • The interface header for a given class must be the first header to be included in the implementation file. This forces the header to be self-contained and not dependent on declarations that happen to be included in the implementation. If it isn't so, the implementation will fail to compile, allowing you to fix the interface to make it self-sufficient.

        // correct                   // error prone
        // Foo.cpp                   // Foo.cpp
      
        #include "Foo.h"             #include <SomethingElse>
        #include <SomethingElse>     #include "Foo.h"
                                     // Now "Foo.h" can depend on SomethingElse without
                                     // us being aware of the fact.
      

    • The Q_DISABLE_COPY macro must appear in the private section of the PIMPL

        // correct                    // wrong
        // Foo.cpp                    // Foo.cpp
      
        class FooPrivate {            class FooPrivate {
          Q_DISABLE_COPY(FooPrivate)  public:
          ...                           Q_DISABLE_COPY(FooPrivate)
        };                               ...
                                      };
      

    PIMPL And Non-QObject Copyable Classes

    The PIMPL idiom allows one to implement copyable, copy- and move- constructible, assignable object. The assignment is done through the copy-and-swap idiom, preventing code duplication. The PIMPL pointer must not be const, of course.

    In C++11, we need to heed the Rule of Four, and provide all of the following: the copy constructor, move constructor, assignment operator, and destructor. And the free-standing swap function to implement it all, of course?.

    We'll illustrate this using a rather useless, but nevertheless correct example.

    Interface

    // Integer.h
    #include <algorithm>
    #include <QScopedPointer>
    
    class IntegerPrivate;
    class Integer {
       Q_DECLARE_PRIVATE(Integer)
       QScopedPointer<IntegerPrivate> d_ptr;
    public:
       Integer();
       Integer(int);
       Integer(const Integer & other);
       Integer(Integer && other);
       operator int&();
       operator int() const;
       Integer & operator=(Integer other);
       friend void swap(Integer& first, Integer& second) /* nothrow */;
       ~Integer();
    };
    

    For performance, the move constructor and the assignment operator should be defined in the interface (header) file. They don't need to access the PIMPL directly:

    Integer::Integer(Integer && other) : Integer() {
       swap(*this, other);
    }
    
    Integer & Integer::operator=(Integer other) {
       swap(*this, other);
       return *this;
    }
    

    All of those use the swap freestanding function, which we must define in the interface as well. Note that it is

    void swap(Integer& first, Integer& second) /* nothrow */ {
       using std::swap;
       swap(first.d_ptr, second.d_ptr);
    }
    

    Implementation

    This is rather straightforward. We don't need access to the interface from the PIMPL, thus Q_DECLARE_PUBLIC and q_ptr are absent.

    // Integer.cpp
    #include "Integer.h"
    
    class IntegerPrivate {
    public:
       int value;
       IntegerPrivate(int i) : value(i) {}
    };
    
    Integer::Integer() : d_ptr(new IntegerPrivate(0)) {}
    Integer::Integer(int i) : d_ptr(new IntegerPrivate(i)) {}
    Integer::Integer(const Integer &other) :
       d_ptr(new IntegerPrivate(other.d_func()->value)) {}
    Integer::operator int&() { return d_func()->value; }
    Integer::operator int() const { return d_func()->value; }
    Integer::~Integer() {}
    

    ?Per this excellent answer: There are other claims that we should specialize std::swap for our type, provide an in-class swap along-side a free-function swap, etc. But this is all unnecessary: any proper use of swap will be through an unqualified call, and our function will be found through ADL. One function will do.

    這篇關(guān)于如何使用 Qt 的 PIMPL 成語?的文章就介紹到這了,希望我們推薦的答案對(duì)大家有所幫助,也希望大家多多支持html5模板網(wǎng)!

    【網(wǎng)站聲明】本站部分內(nèi)容來源于互聯(lián)網(wǎng),旨在幫助大家更快的解決問題,如果有圖片或者內(nèi)容侵犯了您的權(quán)益,請(qǐng)聯(lián)系我們刪除處理,感謝您的支持!

    相關(guān)文檔推薦

    How can I read and manipulate CSV file data in C++?(如何在 C++ 中讀取和操作 CSV 文件數(shù)據(jù)?)
    In C++ why can#39;t I write a for() loop like this: for( int i = 1, double i2 = 0; (在 C++ 中,為什么我不能像這樣編寫 for() 循環(huán): for( int i = 1, double i2 = 0;)
    How does OpenMP handle nested loops?(OpenMP 如何處理嵌套循環(huán)?)
    Reusing thread in loop c++(在循環(huán) C++ 中重用線程)
    Precise thread sleep needed. Max 1ms error(需要精確的線程睡眠.最大 1ms 誤差)
    Is there ever a need for a quot;do {...} while ( )quot; loop?(是否需要“do {...} while ()?環(huán)形?)
    主站蜘蛛池模板: 精品一二三区视频 | 放个毛片看看 | 四虎永久免费黄色影片 | 青青草精品 | 国产精品久久久久久久久久久免费看 | 99色综合 | 综合精品 | 一级片在线观看 | 在线观看a视频 | 欧美在线观看一区 | 国产激情一区二区三区 | 2018国产精品 | 国产精品久久国产精品99 gif | 日本黄色的视频 | 怡红院成人在线视频 | 日本视频在线播放 | 午夜精品久久久久久久星辰影院 | 一级网站 | 久久精品视频99 | 精品成人佐山爱一区二区 | 久久久久久亚洲精品 | 欧美日韩高清在线一区 | 久久综合伊人 | 欧美一区二区三区四区在线 | 日韩一区二区三区在线 | 亚洲高清在线 | 亚洲一区二区免费 | 日韩中文字幕在线视频观看 | 一区二区中文字幕 | 欧美性video 精品亚洲一区二区 | 久久国产一区二区三区 | 日韩欧美在线视频播放 | 国产在线精品一区二区三区 | 在线精品一区二区 | 久久亚洲国产精品日日av夜夜 | 黄色三级免费网站 | 亚洲夜射 | 国产一区二区在线视频 | 夜夜草| 正在播放国产精品 | 密色视频 |