[转]QT中的D指针与Q指针
Qt为了使其动态库最大程度上实现二进制兼容,引入了d指针的概念。
那么为什么d指针能实现二进制兼容呢?
为了回答这个问题,首先弄清楚什么是二进制兼容?
所谓二进制兼容动态库,指的是一个在老版本库下运行的程序,在不经过编译的情况下,仍然能够在新的版本库下运行;需要经过编译才能在新版本下运行,而不需要修改该程序源代码,我们就说该动态库是源代码兼容的。要使一个dll能达到二进制兼容,对于一个结构,对于一个对象,其数据模型应该不变,若有变动,比如在类中增加数据成员或删除数据成员,其结果肯定影响对象的数据模型,从而导致原有数据程员在对象数据模型里的位移发生变化,这样的话编译后的新版本库很可能使程序发生崩溃,为了使在增加和添加项后不使对象数据模型大小发生变化,一种做法是预先分配若干个保留空间,当要添加项时,使用保留项。如下:
class A {
private:
int a;
int reserved[3];
};
class B {
private:
int a;
quint32 b : 1;
quint32 reserved : 31;
};
这样的话,当增加项的时候,只需要利用reserved空间(组态王中好多数据结构就有保留字段,如变量的域),这样的话,对象模型就不会改变。但是这种做法很呆板,因为你不知道未来到底会有多少扩展项,少了不满足要求,多了浪费空间。那么有没有一种更灵活的方法呢?如下:
class Data {
public:
int a;
};
class A {
private:
Data *d_ptr;
};
将A中的成员a放入Data 中,A中放入Data的一个指针,这样的话,无论你向Data中添加多少数据,A的对象模型始终是4个字节的大小(d_ptr指针的大小),这种做法是不是比上面的做法更灵活呢?d_ptr就是我们今天所要说的d指针,Qt为了实现二进制兼容,绝大数类中都包含有这样的指针,下面我们一起来看看Qt的d指针是怎么实现的:
如上图,这个是Qt根结点的指针的一般形式,下面来看看非根结点的一般形式,
注意这里QWidget派生自QObject,它里面没有d_ptr,但是它的成员函数可以访问d_ptr,因为 d_ptr是保护成员,且它的对象模型包含 d_ptr(这是因为派生类继承父类的所有成员)。
下面我们来看看Qt对上述两种情况是怎么实现的:
qobject.h文件:
QObjectData {
public:
QObject *q_ptr;
...
};
class Q_CORE_EXPORT QObject
{
...
Q_DECLARE_PRIVATE(QObject)
public:
Q_INVOKABLE explicit QObject(QObject *parent=0);
virtual ~QObject();
...
protected:
QObject(QObjectPrivate &dd, QObject *parent = 0);
...
protected:
QScopedPointer<QObjectData> d_ptr;
...
};
如上,在这里我省去了其他的项,只保留了于d_ptr有关的项,首先来看看Q_DECLARE_PRIVATE(QObject)是什么:
#define Q_DECLARE_PRIVATE(Class) \
inline Class##Private* d_func() { return reinterpret_cast<Class##Private *>(qGetPtrHelper(d_ptr)); } \
inline const Class##Private* d_func() const { return reinterpret_cast<const Class##Private *>(qGetPtrHelper(d_ptr)); } \
friend class Class##Private;
根据宏定义,则Q_DECLARE_PRIVATE(QObject)翻译如下:
inline QObjectPrivate *d_func()
{
return reinterpret_cast<QObjectPrivate *>(qGetPtrHelper(d_ptr));
}
inline const QObjectPrivate *d_func() const
{
return reinterpret_cast<const QObjectPrivate *>(qGetPtrHelper(d_ptr));
}
friend class QObjectPrivate;
再来看看qGetPtrHelper的定义:
template <typename T> static inline T *qGetPtrHelper(T *ptr)
{
return ptr;
}
再来看QScopePointer,它类似于智能指针,这样不用关心 d_ptr的释放,当离开QScopePointer的作用范围,QScopePointer会自动释放d_ptr指向的堆内存,那么这个指针是什么时候生成的呢?q_ptr又是什么时候赋值的呢?让我们来看看qobject.cpp的实现:
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_ptr指向new QObjectPrivate,而QObjectPrivate派生自QObjectData,那么Q_D(QObject)宏表示什么意思呢?
#define Q_D(Class) Class##Private * const d = d_func()
Q_D(QObject);翻译如下:
QObjectPrivate * const d = d_func();
不难看出Q_D(QObject);定义了一个QObjectPrivate的常量指针,指向d_func() 的返回值,而该返回值,正是d_ptr(见头文件 d_func()的定义),因此通过Q_D宏我们就可以访问d指针了。
对于第二个构造函数稍后介绍,下面来看看非根结点的d_ptr的实现情况:
头文件:
class Q_CORE_EXPORT QObjectPrivate : public QObjectData
{
Q_DECLARE_PUBLIC(QObject)
...
};
class Q_GUI_EXPORT QWidgetPrivate : public QObjectPrivate
{
Q_DECLARE_PUBLIC(QWidget)
...
};
class Q_GUI_EXPORT QWidget : public QObject
{
...
Q_DECLARE_PRIVATE(QWidget)
...
public:
...
explicit QWidget(QWidget* parent = 0, Qt::WindowFlags f = 0);
...
};
我们首先来看看Q_DECLARE_PUBLIC宏:
#define Q_DECLARE_PUBLIC(Class) \
inline Class* q_func() { return static_cast<Class *>(q_ptr); } \
inline const Class* q_func() const { return static_cast<const Class *>(q_ptr); } \
friend class Class;
根据宏定义,Q_DECLARE_PUBLIC(QObject)翻译如下:
template <typename T> static inline T *qGetPtrHelper(T *ptr)
{
return ptr;
}
inline QObject *q_func()
{
return static_cast<QObject *>(q_ptr);
}
inline const QObject *q_func() const
{
return static_cast<const QObject *>(q_ptr);
}
friend class QObject;
Q_DECLARE_PUBLIC(QWidget)翻译如下:
inline QWidget *q_func()
{
return static_cast<QWidget *>(q_ptr);
}
inline const QWidget *q_func() const
{
return static_cast<const QWidget *>(q_ptr);
}
friend class QWidget;
注意这里的q_ptr是在QObjectData里公有声明的,QObjectPrivate,QWidgetPrivate都派生或间接派生自QObjectData,所以可以访问q_ptr。
接下来看Q_DECLARE_PRIVATE(QWidget)的翻译:
inline QWidgetPrivate *d_func()
{
return reinterpret_cast<QWidgetPrivate *>(qGetPtrHelper(d_ptr));
}
inline const QWidgetPrivate *d_func() const
{
return reinterpret_cast<const QWidgetPrivate *>(qGetPtrHelper(d_ptr));
}
friend class QWidgetPrivate;
接下来看看QWidget的构造函数的实现:
QWidget::QWidget(QWidget *parent, Qt::WindowFlags f)
: QObject(*new QWidgetPrivate, 0)
{
...
}
看到QObject(*new QwidgetPrivate, 0)这里调用了QObject的第二个构造函数,将d_ptr指向new QWidgetPrivate所指向的堆内存。
还可以参考https://wiki.qt.io/D-Pointer/zh,这个讲的不错!
[转]QT中的D指针与Q指针的更多相关文章
- Qt编程之d指针与q指针
我们在Qt中可以看到两个宏Q_D和Q_Q这两个红分别是取得d指针和q指针的,d指针指向封装的私有类,q指针指向公共的类.(我的理解类似于回调,回指的意思). 为什么Qt要这样实现呢?下面几个链接中的文 ...
- Qt内部的d指针和q指针手把手教你实现
Qt内部的d指针和q指针 在讲Qt的D指针之前让我们来简单的解释一下D指针出现的目的,目的是什么呢?保证模块间的二进制兼容. 什么是二进制兼容呢,简单说就是如果自己的程序使用了第三方模块,二进制兼容可 ...
- Qt中的Q_D宏和d指针
_ZTS7QObject 一.Q_D的在文件中的提法 Q_D的设置意在方便地获取私有类指针,文件为qglobal.h.下面的##是宏定义的连字符.假设类名是A,那么A##Private翻译过来就是AP ...
- Qt中的串口编程之三
QtSerialPort 今天我们来介绍一下QtSerialPort模块的源代码,学习一下该可移植的串口编程库是怎么实现的. 首先,我们下载好了源代码之后,使用QtCreator打开整个工程,可以看到 ...
- Qt中无处不在的d指针为何方神圣
在研究QCoreApplication类的代码时,无意间弄明白了“d_func()”和“d指针”的来源: class Q_CORE_EXPORT QCoreApplication#ifndef QT_ ...
- Qt中的ui指针和this指针
初学qt,对其ui指针和this指针产生疑问,画了个把小时终于搞懂了. 首先看ui指针的定义: 在mainwindow.h中 private: Ui::MainWindow *ui; Ui又是什么? ...
- 智能指针类模板(中)——Qt中的智能指针
Qt中的智能指针-QPointer .当其指向的对象被销毁时,它会被自动置空 .析构时不会自动销毁所指向的对象-QSharedPointer .引用计数型智能指针 .可以被自由的拷贝和赋值 .当引用计 ...
- PyQt(Python+Qt)学习随笔:Qt中的部分类型QString、QList和指针、引用在PyQt中的实现方式
老猿Python博文目录 老猿Python博客地址 在我们查阅Qt的文档资料时,可以看到Qt中的链表使用的是QList,字符串使用的是QString,但老猿在测试时发现这两个类型PyQt不支持,无法找 ...
- Qt 中使用Singleton模式需小心
在qt中,使用Singleton模式时一定要小心.因为Singleton模式中使用的是静态对象,静态对象是直到程序结束才被释放的,然而,一旦把该静态对象纳入了Qt的父子对象体系,就会导致不明确的行为. ...
随机推荐
- 几个python编程例子
作业 有如下值集合[11,22,33,44,55,66,77,88,99,90...],将所有大于 66 的值保存至字典的第一个key中,将小于 66 的值保存至第二个key的值中,结果为{'k1': ...
- nginx 配置使用index.php作为目录的默认加载文件
配置如下: 在server增加一行: index index.php index.html index.htm default.php default.htm default.html 增加后如下: ...
- 深度学习accuracy
accuracy=(1+3)/(1+2+3+4),即在所有样本(例子)中做出正确预测的的比例,或者说正确预测的样本数占总预测样本数的比值. precision=(1)/(1+2),指的是正确预测的正样 ...
- oracle中having与where的区别
1.where 不能放在group by 的后面 2.HAVING 是跟GROUP BY 连在一起用的,放在GROUP BY 后面,此时的作用相当于WHERE 3.WHERE 后面的条件中不能有聚集函 ...
- QT控制文本框输入内容
利用正则表达式,例: //即账号最长为10位,只能由数字组成 QRegExp regx("[0-9]{1,10}"); QValidator *validator = new QR ...
- RHEL7 安装Docker-CE
rhel7官方有源可以直接使用,前提是需要订阅, 参考地址 通过添加CentOS7 源,进行安装: 通过添加CentOS7 源,进行安装 参考博客 安装container-selinux依赖(Requ ...
- requests--文件上传,文件下载
文件上传 在做接口自动化的时候,有时需要上传文件,比如更改头像等等,在request里,通过files参数来上传 import requests base_url = 'http://httpbin. ...
- [LeetCode] 380. Insert Delete GetRandom O(1) 常数时间内插入删除和获得随机数
Design a data structure that supports all following operations in average O(1) time. insert(val): In ...
- [LeetCode] 80. Remove Duplicates from Sorted Array II 有序数组中去除重复项之二
Given a sorted array nums, remove the duplicates in-place such that duplicates appeared at most twic ...
- subprocess实用手册
背景 python执行操作系统的命令,如python执行shell命令 subprocess模块主要用于创建子进程,并连接它们的输入.输出和错误管道,获取它们的返回状态.通俗地说就是通过这个模块,你可 ...