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"。
原创造福大家,共享改变世界
献出一片爱心,温暖作者心灵