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"。


原创造福大家,共享改变世界

献出一片爱心,温暖作者心灵


qt creator源码全方面分析(4-1)的更多相关文章

  1. qt creator源码全方面分析(3-3)

    目录 qtcreatordata.pri 定义stripStaticBase替换函数 设置自定义编译和安装 QMAKE_EXTRA_COMPILERS Adding Compilers 示例1 示例2 ...

  2. qt creator源码全方面分析(3-5)

    目录 qtcreatorlibrary.pri 使用实例 上半部 下半部 结果 qtcreatorlibrary.pri 上一章节,我们介绍了src.pro,这里乘此机会,把src目录下的所有项目文件 ...

  3. qt creator源码全方面分析(0)

    本人主攻C++和Qt. 上两天刚研究完Qt install framework(IFW)应用程序安装框架. google没发现有正儿八经的官方文档的翻译,我就进行了翻译哈!! 系列文章具体见:http ...

  4. qt creator源码全方面分析(4-0)

    Qt系统 Qt Creator源码是在Qt对象和框架基础下写的,因此,阅读Qt Creator源码,你首先对Qt得有一定的了解. Qt Core Qt Core特征: The Meta-Object ...

  5. qt creator源码全方面分析(4-2)

    目录 global头文件 global.h xx.h global头文件 插件的本质就是动态链接库,对于库,需要导出符号,供用户导入使用.在qt creator的源码中,存在固定的导入导出模式. gl ...

  6. qt creator源码全方面分析(4-5)

    目录 Qt中的字符串 QLatinString 详细介绍 源码 小结 QStringLiteral(str) 详细介绍 源码 小结 Qt中的字符串 Qt中处理字符串最常用的肯定是QString,但是在 ...

  7. qt creator源码全方面分析(4-6)

    目录 Qt插件基础 Qt插件基础 我们知道Qt Creator源码是基于插件架构的,那么我们先来介绍下插件基础知识. 相关内容如下: How to Create Qt Plugins [ - Defi ...

  8. qt creator源码全方面分析(3-2)

    目录 qtcreator.pri 判断重复包含 定义版本信息 VERSION 定义IDE名称 启用C++14 CONFIG 自定义函数 Replace Functions Test Functions ...

  9. qt creator源码全方面分析(2-7)

    目录 Completing Code 补全代码片段 编辑代码片段 添加和编辑片段 删除片段 重置片段 补全Nim代码 Completing Code 在编写代码时,Qt Creator建议使用属性,I ...

  10. qt creator源码全方面分析(2-10-1)

    目录 Getting and Building Qt Creator 获取Qt 获取和构建Qt Creator Getting and Building Qt Creator 待办事项:应该对此进行扩 ...

随机推荐

  1. VS2019 C++动态链接库的创建使用(4) - C语言客户端

    前面提到过C++具有函数重载功能,所以引出的函数名会有变化,即名称改编,如果是C语言编写的客户端则无法正确识别. 处理方法: ①只需在宏定义中间增加 extern "C",但这种使 ...

  2. JAVA为什么不能通过构造函数传参来设置数组长度。

     今天我们来说说 JAVA通过构造函数传递的参数来设置数组长度的问题. 问题在于我们没有明确知晓JVM的运行顺序.在new对象的时候,先调用构造函数,但是并没有将执行构造函数的代码,随机之后就初始化了 ...

  3. SQLServer——MASTER..spt_values

    常常见到这个表,人家用得天花乱坠的. 自己select一看却莫名其妙的. 如上, 这个表主要用来保存一些枚举值, 据说是从sybase继承过来,许多函数和存储过程可以看到它的身影.也可以叫系统常量表吧 ...

  4. Linux软件安装之JDK的安装

    JDK的安装 1.1. 下载JDK,此处版本是1.8u131,实际操作以自己具体版本为准 先查看Linux系统是多少位(32位/64位):getconf LONG_BIT 然后去官网下载JDK [jd ...

  5. POJ1144 tarjan+网络中割点与割边的数量

    题目链接:http://poj.org/problem?id=1144 割点与割边的数量我们可以通过tarjan的思想从一个点开始对其余点进行访问.访问的顺序构成一棵dfs树,其中根节点到任何一个结点 ...

  6. CF1324A Yet Another Tetris Problem 题解

    原题链接 简要题意: 再简要一波: 每次可以把一个数增加 \(2\),问最后能不能让所有数相等.(也就是抵消掉) 什么?题意变成这样子还做个啥? 你会发现,必须所有数的奇偶性都相同,才可以:反之就不可 ...

  7. laravel如何实现多用户体系登录

    laraveli添加一个或多个用户表,以admin为例. 部分文件内容可能需要根据实际情况修改 创建一个Admin模型 php artisan make:model Admin -m 编写admins ...

  8. 北邮oj 104. 912星球的研究生

    104. 912星球的研究生 时间限制 1000 ms 内存限制 65536 KB 题目描述 最近912星球的研究生入学了,912星球的教务处因此忙的焦头烂额,要求yzr做一个信息管理系统登陆查询研究 ...

  9. 面试刷题25:jvm的垃圾收集算法?

    垃圾收集是java语言的亮点,大大提高了开发人员的效率. 垃圾收集即GC,当内存不足的时候触发,不同的jvm版本算法和机制都有差别. 我是李福春,我在准备面试,今天的问题是: jvm的垃圾回收算法有哪 ...

  10. 【笔记3-31】Python语言基础-字典dict

    创建字典 dict1 = {'k1': 'v1', 'k2': 'v2', 'k3': 'v3', 'k4': 'v4'} dict2 = dict(k1='v1', k2='v2', k3='v3' ...