1. 程式人生 > >qt creator原始碼全方面分析(4-1)

qt creator原始碼全方面分析(4-1)

目錄
  • d指標和q指標
    • 簡單示例
    • q指標
    • QObject和QObjectPrivate
    • qtcreator中的變體1
    • qtcreator中的變體2
    • 小結

d指標和q指標

我們在類成員名稱和使用d指標中,已經介紹過了d指標。

這是一個絕妙的技巧,能夠在不破壞二進位制相容性的情況下將新的私有資料成員新增到類中。此外,它還能保持標頭檔案的乾淨,並隱藏具體的實現,加速編譯。

簡單示例

// foo.h
class FooPrivate;

class Foo
{
public:
    Foo();
    int getData() const;
    void setData(int d);
private:
    FooPrivate* d;
};

// foo.cpp
class FooPrivate {
public:
    FooPrivate() : data(0) {}
    int data;
};

Foo::Foo() : d(new FooPrivate) {}

int Foo::getData() const { return d->data; }

void Foo::setData(int d) { d->data = d; }

Foo類中只暴露了介面,具體的實現和資料都隱藏到了cpp檔案的FooPrivate類中。

q指標

d指標,用於在公有類中訪問對應的私有類。對應的,q指標,用於在私有類中反向訪問器對應的公有類。

我們可以對上面的程式碼進行簡單的修改

// foo.cpp
class FooPrivate {
public:
    FooPrivate(Foo *f) : data(0), q(f) {}
    int data;
    Foo *q;
};

Foo::Foo() : d(new FooPrivate(this)) {}

QObject和QObjectPrivate

// qobject.h
class Q_CORE_EXPORT QObjectData {
public:
    virtual ~QObjectData() = 0;
    QObject *q_ptr;
    QObject *parent;
    QObjectList children;

    uint isWidget : 1;
    uint blockSig : 1;
    uint wasDeleted : 1;
    uint isDeletingChildren : 1;
    uint sendChildEvents : 1;
    uint receiveChildEvents : 1;
    uint isWindow : 1; //for QWindow
    uint unused : 25;
    int postedEvents;
    QDynamicMetaObjectData *metaObject;
    QMetaObject *dynamicMetaObject() const;
};

class Q_CORE_EXPORT QObject
{
    Q_OBJECT
    Q_PROPERTY(QString objectName READ objectName WRITE setObjectName NOTIFY objectNameChanged)
    Q_DECLARE_PRIVATE(QObject)
    
public:
    Q_INVOKABLE explicit QObject(QObject *parent=Q_NULLPTR);
    ....
protected:
    QObject(QObjectPrivate &dd, QObject *parent = Q_NULLPTR);
protected:
    QScopedPointer<QObjectData> d_ptr;
    ....
}

// qobject_p.h
class Q_CORE_EXPORT QObjectPrivate : public QObjectData
{
    Q_DECLARE_PUBLIC(QObject)
    ...
}

QObject中的的d_ptr就是d指標,QObjectData中的q_ptr就是q指標。

qobject.h是提供給使用者的可見的標頭檔案,qobject_p.h是內部私有的標頭檔案。

在簡單示例中,我們把私有類定義在了cpp檔案中,如果私有類太大,當然可以單獨定義一個頭檔案,然後在cpp檔案中進行引用。

// qobject.cpp
#include "qobject.h"
#include "qobject_p.h"
...

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

上面的建構函式的內容就不言而喻了,對d指標和q指標進行賦值。

對於第二個受保護的建構函式,具體幹什麼用的,留給大家自行學習了,提示一下,可以前往qwidget.cpp中進行檢視,用於在派生類中對上一個基類進行賦值。

這裡大家不知道發現沒有,有幾個特殊的巨集,Q_DECLARE_PUBLIC,Q_DECLARE_PRIVATE,Q_D,其實還有Q_Q。這些巨集大家可以前往qglobal.h中檢視。最終的作用是為了方便訪問d指標和q指標,在函式中宣告Q_D或Q_Q以後,可以直接使用d和q變數來代替d指標和q指標了。

關於d指標和q指標的更多的資訊,請參考https://wiki.qt.io/D-Pointer

qtcreator中的變體1

qtcreator由於使用了外掛機制,相當一部分暴露出來的元件都是單例模式,使用如下模式獲取控制代碼。

static T *instance();

因此產生了一些變體。示例如下:

// foo.h
class FooPrivate;

class Foo
{
    friend class FooPrivate;
public:
    static Foo *instance();
private:
    Foo();
    ~Foo();
    ...
};

// foo_p.h
class Foo;

class FooPrivate {
public:
    FooPrivate(Foo *qf) : q(qf) {}
private:
    Foo *q;
    ...
};

// foo.cpp
#include "foo.h"
#include "foo_p.h"
...

static FooPrivate *d = 0;
static Foo *m_instance = 0;

Foo *Foo::instance()
{
    return m_instance;
}

Foo::Foo()
{
    m_instance = this;
    d = new FooPrivate(this);
}

Foo::~Foo()
{
    delete d;
    d = 0;
    m_instance = 0;
}

這裡主要的變化在於,d指標不再是Foo的成員了,Foo和FooPrivate都是定義在cpp的靜態變數,在Foo的建構函式中初始化。這樣,在Foo的成員函式中,也能直接使用d指標。

qtcreator中的變體2

此外,還有一種變體。如果只想暴露介面類,供使用者呼叫,那麼可以隱藏具體的實現類,並通過其他的管理類的相關成員函式來返回介面類指標。

// foo.h
class Foo
{
public:
    void algo() = 0;
}

// foo_p.h
class FooPrivate : public Foo
{
public:
    void algo() override;
protect:
    void doAlgo() { }
}

// foo.cpp
void FooPrivate::algo() { doAlgo(); }
void FooPrivate::doAlgo() {  }

// foomanager.h
class FooManager
{
    static Foo *foo();
}

對使用者只提供foo.h和foomanager.h即可,把細節和具體實現都封裝起來。使用者只能通過FooManager的函式獲取foo控制代碼,並通過foo控制代碼呼叫介面。

小結

其實,只要掌握了原理,各種變化就隨意了。

  1. 對於管理類來說,一般是單例模式的,這種情況下,我們可以在cpp檔案中定義靜態的m_instance和d,如變體1。
  2. 對於非管理類,譬如QObject,可以建立類的多個例項的,我們一般需要在公有類中把私有類指標d作為成員變數,如簡單示例。

最後,提醒一點,如果在cpp中使用Q_OBJECT,請注意先使用moc工具建立xx_moc.cpp,並#include到該cpp中,否則會報錯的,"undefined reference to vtable"。


原創造福大家,共享改變世界

獻出一片愛心,溫暖作者心靈