d指針和q指針
這是一個絕妙的技巧,能夠在不破壞二進制兼容性的情況下將新的私有數據成員添加到類中。此外,它還能保持頭文件的干凈,並隱藏具體的實現,加速編譯。
簡單示例
// 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句柄調用接口。
小結
其實,只要掌握了原理,各種變化就隨意了。
- 對於管理類來說,一般是單例模式的,這種情況下,我們可以在cpp文件中定義靜態的m_instance和d,如變體1。
- 對於非管理類,譬如QObject,可以創建類的多個實例的,我們一般需要在公有類中把私有類指針d作為成員變量,如簡單示例。
最后,提醒一點,如果在cpp中使用Q_OBJECT,請注意先使用moc工具創建xx_moc.cpp,並#include到該cpp中,否則會報錯的,"undefined reference to vtable"。
原創造福大家,共享改變世界
獻出一片愛心,溫暖作者心靈
