d指针在Qt上的应用及实现

2023-11-14

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根结点的指针的一般形式,下面来看看非根结点的一般形式,


注意这里QWidge派生自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)翻译如下:

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所指向的堆内存。


本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

d指针在Qt上的应用及实现 的相关文章

随机推荐

  • 程序员修仙之路--优雅快速的统计千万级别uv(留言送书)

    菜菜 咱们网站现在有多少PV和UV了 Y总 咱们没有统计pv和uv的系统 预估大约有一千万uv吧 写一个统计uv和pv的系统吧 网上有现成的 直接接入一个不行吗 别人的不太放心 毕竟自己写的 自己拥有主动权 给你两天时间 系统性能不要太差呀
  • Head First 设计模式 C#实现

    Head First 设计模式 文章目录 Head First 设计模式 完整源码 设计模式入门 具体设计模式 策略模式 观察者模式 装饰者模式 工厂模式 抽象工厂模式 单例模式 命令模式 适配器模式 外观模式 模版方法模式 迭代器模式 组
  • 指针以及内存分配

    1 指针很灵活 这使得指针很难管理 在定义指针时 将在栈中开辟一块内存存放指针的地址 栈内的内存由系统分配和释放 指针的地址内存只是存放指针的地址 不存放指针指向的数据 值得注意的是 定义指针时指针会随机指向一块内存 如int p p会指向
  • 243. 一个简单的整数问题2(树状数组)

    输入样例 10 5 1 2 3 4 5 6 7 8 9 10 Q 4 4 Q 1 10 Q 2 4 C 3 6 3 Q 2 4 输出样例 4 55 9 15 解析 一般树状数组都是单点修改 区间查询或者单点查询 区间修改 这道题都是区间操作
  • 从C语言到C++(语法基础一)

    一 关键字的添加 C 是对C的 增强 几乎引入了一倍的关键字 C语言常用关键字 C 98关键字 二 命名空间 在写C语言程序时 当你写了一个函数把它命名为max 且此时包含头文件stdlib h时你会发现编译会报错 这是C语言的缺陷之一 因
  • 【漏洞复现】 Sudo缓存溢出提权漏洞(CVE-2021-3156)

    说明 此博客为本人的漏洞复现学习过程记录 前言 漏洞原理 本次的漏洞存在于Sudo上 一个基于堆的缓冲区溢出漏洞 CVE 2021 3156 该漏洞被命名为 Baron Samedit 在sudo解析命令行参数的方式中发现了基于堆的缓冲区溢
  • C++面试知识点

    strcpy函数实现 char strcpy char dest const char src assert dest NULL src NULL 检查指针的有效性 char res dest while dest src 0 return
  • Idea 插件下载缓慢,无法下载的解决方式

    要给idea装一个插件 但今天的idea死活下不下来插件 总报错 Plugin JProfiler was not installed Cannot download https plugins jetbrains com pluginMa
  • 杨桃的Python进阶讲座17——数组array(七)三维数组和n维数组的索引和取值(配详细图解)

    本人CSDN博客专栏 https blog csdn net yty 7 Github地址 https github com yot777 三维数组的索引和取值 创建一个numpy三维数组z 如下所示 gt gt gt import num
  • Nginx官方文档(三十四)【ngx_http_ssl_module】

    ngx http ssi module 示例配置 指令 ssl ssl buffer size ssl certificate ssl certificate key ssl ciphers ssl client certificate s
  • 电脑报错vcomp100.dll丢失怎样修复?这三个方法可以解决

    vcomp100 dll是微软Visual C 2005 Redistributable Package的一部分 它包含了运行某些程序所需的C 运行时库 当电脑中的vcomp100 dll文件丢失或损坏时 可能会导致一些程序无法正常运行 甚
  • [springboot 项目启动类Application.java运行没有任何反应]

    1 问题 最近从网上找了一个springboot项目学习 发现项目启动类无法运行 运行没有任何反应 maven依赖检查没有任何问题 2 解决方案 Files Setting Plugins Groovy勾选 再次运行 成功 3
  • Python: 装饰器和语法糖

    一 Python 装饰器 Python 装饰器本身就是一个函数 它的作用是装饰一个其他的函数 但是不改变原有的程序功能 还要增添新的功能 调用函数时的接口没有变化 比如 装修一个房子 如果不隔音 我在墙上加一层隔音板 却不能把墙拆了 换成隔
  • C# 关于浏览器——WebBrowser篇

    最近要写一个浏览器包裹一个网站 试了各种浏览器插件 记录一下 第一个就是微软的WebBrowser 这个很容易 直接拖过来 然后写一下注册表调用IE11的内核显示 这个代码是抄的
  • python金融数据分析马伟明_Python金融数据分析

    前言 第1章Python在金融中的应用 1 1Python适合我吗 1 1 1免费 开源 1 1 2高级 强大 灵活的编程语言 1 1 3丰富的标准库 1 2面向对象编程与函数式编程 1 2 1面向对象式方法 1 2 2函数式方法 1 2
  • docker day04

    Dockerfile FORM 1 指定基础镜像 可以起别名 也可以指定多个FROM指令 用于多阶段构建 2 加载触发器 加载ONBUILD指令 3 不指定基础镜像 声明当前镜像不依赖任何镜像 官方保留字 scratch RUN 1 在容器
  • 循序渐进,学会用pyecharts绘制瀑布图

    循序渐进 学会用pyecharts绘制瀑布图 瀑布图简介 瀑布图 Waterfall Plot 是由麦肯锡顾问公司所独创的图表类型 因为形似瀑布流水而称之为瀑布图 瀑布图采用绝对值与相对值结合的方式 适用于表达多个特定数值之间的数量变化关系
  • 串口调试助手与CH340驱动分享

    串口调试助手与CH340驱动分享 分享以下资源给大家 包括CH340与CH341驱动 野火以及正点原子的串口调试助手 网盘链接 链接 https pan baidu com s 1cARKBdzJhDcrQrBSfs2Nlw 提取码 fxv
  • python: PyCharm 2023.1打包项目成执行程序

    IDE 最底部 pyinstaller i heart ico D main py 生成成功
  • d指针在Qt上的应用及实现

    Qt为了使其动态库最大程度上实现二进制兼容 引入了d指针的概念 那么为什么d指针能实现二进制兼容呢 为了回答这个问题 首先弄清楚什么是二进制兼容 所谓二进制兼容动态库 指的是一个在老版本库下运行的程序 在不经过编译的情况下 仍然能够在新的版