Qt 的 d_ptr (d-pointer) 和 q_ptr (q-pointer)解析;Q_D和Q_Q指针

news2025/1/16 5:12:45

篇一:

Qt之q指针(Q_Q)d指针(Q_D)源码剖析---源码面前了无秘密_qtq指针-CSDN博客

通常情况下,与一个类密切相关的数据会被作为数据成员直接定义在该类中。然而,在某些场合下,我们会将这些数据从该类(被称为公类)分离出来,定义在一个单独的类中(被称为私类)。公类中会定义一个指针,指向私类的对象。在计算机的发展历史中,这种模式被称为pointer to implementation (pimpl)。

Qt常将其命名为d_ptr或者d。Qt文档将其称为d-pointer。与之对应还有q_ptr或者q。

所以,q,d指针并不是多么神秘,它只是运用了pimpl手法,将数据成员放到了另外一个类中。
那么接下来,在了解了pimpl手法后,可以看下面的d,q指针了。

Q_D和Q_Q指针
在一些项目中会遇到这两者,简称 D 指针和 Q 指针。Qt中大量使用Q_D和Q_Q。

Q_D 与 Q_Q宏定义
#define Q_D(Class) Class##Private * const d = d_func() 
#define Q_Q(Class) Class * const q = q_func() 
两个宏展开后分别是对 d_func() 和 q_func() 两个函数的调用,返回值分别赋值给 d 和 q 两个指针变量。

=》双井号:连接作用直接替换类名就行。Q_D(QFocusFrame)返回的是Class##Private * const 类型指针,替换掉Class后,就是QFocusFramePrivate* const 类型指针,即为d指针!

Q_DECLARE_PRIVATE 与 Q_DECLARE_PUBLIC
那么 d_func() 和 q_func()又是什么呢?那就需要看这两个宏的定义

#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;  

#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;

这里涉及到宏的一些技巧,比如##连接符,\续行符。如果现阶段看不懂没关系,可以看下面的简化版

ClassPrivate* d_func() { return reinterpret_cast<ClassPrivate *>(d_ptr)); } 

大致意思,d_func()其实就是d_ptr,只是经过了转型等处理。那么,你就可以理解为d = f_func() = d_ptr(仅限于初学者的初期阶段理解)

然后,代码里面还有一行friend class Class##Private; ,这个就是用来声明友元的,目的是A类可以访问B类的私有成员。这也是实现pimpl的基础。


接下来,直接放上代码来看看:

class MyClassPrivate;  //前置声明
class MyClass : public QObject {
 public:
    MyClass(QObject *parent = nullptr);
    virtual ~MyClass();
    void dummyFunc();
 signals : 
    void dummySignal();

 private:
    MyClassPrivate *const d_ptr;
    Q_DECLARE_PRIVATE(MyClass);//①
};

class MyClassPrivate {
 public:
    MyClassPrivate(MyClass *parent) : q_ptr(parent) {}

    void foobar() {
        Q_Q(MyClass);  //声明之后,即将q_func() = q,就可以直接使用q
        emit q->dummySignal();
    }

 private:
    MyClass *const q_ptr;
    Q_DECLARE_PUBLIC(MyClass);//②
};

MyClass::MyClass(QObject *parent)
          :QObject(parent),
          d_ptr(new MyClassPrivate(this)) {
}

MyClass::~MyClass() {
    Q_D(MyClass);  //同Q_Q
    delete d;
}

void MyClass::dummyFunc() {
    Q_D(MyClass);
    d->foobar();
}

在这段代码中,公类MyClass定义了一个指针d_ptr来访问私类MyClassPrivate,而私类定义了一个指针q_ptr来访问公类。在这个简单的例子中,公类私类本可以通过这两个指针非常方便地相互访问对方的数据。但是,在一些复杂的场合下,这两个指针并不是直接定义在公类、私类中的,而是被定义在它们的基类中。此时,就需要用到Qt定义的4个宏:Q_DECLARE_PRIVATE与Q_DECLARE_PUBLIC、Q_D和Q_Q。

Q_DECLARE_PRIVATE(MyClass)作用是:在公类中定义了一个成员函数d_func,返回一个指针,指向对应的私类。

Q_DECLARE_PUBLIC(MyClass)作用是:在私类中定义了一个成员函数p_func,返回一个指针,指向对应的公类。

本例中,公类本身定义了一个指向私类的指针d_ptr,所以Qt应用程序可以直接使用这个指针,而不必调用d_func来获取这个指针。

总之,一方面,Qt在公类中定义了一个指针d_ptr指向私类,在宏Q_DECLARE_PRIVATE中定义了一个函数获取这个指针,用宏Q_D将这个指针重新命名为d,以便于访问私类对象。另一方面,Qt在私类中定义了一个指针q_ptr指向公类,在宏Q_DECLARE_PUBLIC中定义了一个函数获取这个指针,用宏Q_Q将这个指针重新命名为q,以便于访问公类对象。

参考资料
张波 《Qt中的C++技术》              
原文链接:https://blog.csdn.net/no_say_you_know/article/details/123921937

篇二:

在讲解d/q指针之前,我们先了解一下什么是Pimpl,这样更有助于我们理解Qt这么做的目的。

一、Pimpl(Pointer to Implementation)简介
该技术是一种减少代码依赖和编译时间的C++编程技巧,Pimpl的基本思想是:将类的私有数据成员指针化,并将其移动到类的实现文件中。这样,在公共头文件中定义的类只包含指向私有实现的指针,而不是私有实现本身。这使得实现的细节可以在不更改类的公共接口的情况下进行更改。

Pimpl技术通过在类中使用指向实现类的指针隐藏类的实现细节。在使用Pimpl技术时,开发人员需要在类的头文件中声明一个私有的指向实现类的指针,并在类的实现文件中定义实现类。通过这种方式,开发人员可以将实现细节类的接口分离开来,从而提高了代码的可读性和可维护性。

以下是一个使用Pimpl技术的示例:

// MyClass.h

class MyClass
{
public:
    MyClass();
    ~MyClass();

    void doSomething();

private:
    class Impl;
    Impl* m_pImpl;
};

// MyClass.cpp

class MyClass::Impl
{
public:
    void doSomethingImpl();
};

MyClass::MyClass() : m_pImpl(new Impl)
{}

MyClass::~MyClass()
{
    delete m_pImpl;
}

void MyClass::doSomething()
{
    m_pImpl->doSomethingImpl();
}

void MyClass::Impl::doSomethingImpl()
{
    // Implementation details
}

在上面的示例中,MyClass类包含一个私有的Impl指针,指向一个名为Impl的内部实现类。实现类包含doSomethingImpl()方法的实现细节,而MyClass只暴露了doSomething()方法。在MyClass的构造函数中,我们分配了一个新的Impl对象并将其分配给m_pImpl指针。在MyClass的析构函数中,我们删除了m_pImpl指针指向的对象,以避免内存泄漏。

Pimpl技术的优缺点
优点:
1.隐藏实现细节:Pimpl技术使开发人员能够将实现细节与类的接口分离开来,从而降低耦合,提高代码的可读性和可维护性。
2.减少编译依赖性:Pimpl技术可以帮助减少类的头文件中的依赖项,从而加快编译时间并减少不必要的重新编译。(此项对于大型项目来说非常有用)
3.提高二进制兼容性:由于Pimpl技术将实现细节从类的接口中分离出来,因此可以在不破坏二进制兼容性的情况下修改类的实现。

缺点:
1.增加间接调用:由于Pimpl技术使用指针来引用私有实现,因此在访问私有实现时需要进行额外的间接调用,这可能会影响性能。
2.内存开销:Pimpl技术需要在堆上分配和管理额外的内存,这会导致一些开销。

Pimpl技术我们已经了解,现在我们开始看一下Qt的实现以及原理吧:

二、Qt源码中的d指针/q指针
下面我们将QObject的源码作为例子进行讲解:

qobject.h

// QObjectData
class Q_CORE_EXPORT QObjectData {
    Q_DISABLE_COPY(QObjectData)
public:
    QObjectData() = default;
    virtual ~QObjectData() = 0;
    QObject *q_ptr;  // q指针
    QObject *parent;
    QObjectList children;
    ....
};
    
// QObject
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=nullptr);
    virtual ~QObject();

    virtual bool event(QEvent *event);
    virtual bool eventFilter(QObject *watched, QEvent *event);

    ...
protected:
    QScopedPointer<QObjectData> d_ptr;  // d指针
    ...

};

qobject.cpp

QObject::QObject(QObject *parent)
    : QObject(*new QObjectPrivate, parent)
{
}

/*!
    \internal
 */
QObject::QObject(QObjectPrivate &dd, QObject *parent)
    : d_ptr(&dd)
{
    Q_ASSERT_X(this != parent, Q_FUNC_INFO, "Cannot parent a QObject to itself");

    Q_D(QObject);//Q_D宏获取d_ptr
    d_ptr->q_ptr = this;
    auto threadData = (parent && !parent->thread()) ? parent->d_func()->threadData.loadRelaxed() : QThreadData::current();
    threadData->ref();
    d->threadData.storeRelaxed(threadData);  // 此处d变量:即是Q_D宏获取的d_ptr
    ...
}


我们再来看一下相关的宏定义:

qglobal.h

// The body must be a statement:
#define Q_CAST_IGNORE_ALIGN(body) QT_WARNING_PUSH QT_WARNING_DISABLE_GCC("-Wcast-align") body QT_WARNING_POP
#define Q_DECLARE_PRIVATE(Class) \
    inline Class##Private* d_func() \
    { Q_CAST_IGNORE_ALIGN(return reinterpret_cast<Class##Private *>(qGetPtrHelper(d_ptr));) } \
    inline const Class##Private* d_func() const \
    { Q_CAST_IGNORE_ALIGN(return reinterpret_cast<const Class##Private *>(qGetPtrHelper(d_ptr));) } \
    friend class Class##Private;

#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;

#define Q_D(Class) Class##Private * const d = d_func()
#define Q_Q(Class) Class * const q = q_func()

Q_DECLARE_PRIVATEQ_DECLARE_PUBLIC是Qt用来在类的头文件中声明获取d指针与q指针的私有函数,其核心在于添加了强制类型转换。
Q_D与Q_Q两个宏是用来获取d/q常量指针的,在函数中可以直接使用 d变量/q变量 代替 d_ptr与q_ptr,因为通过它们获取的指针类型是具体的,所以是直接使用ptr变量代替不了的。

我们再来看看qGetPtrHelper这个函数的定义:

qglobal.h

template <typename T> inline T *qGetPtrHelper(T *ptr) { return ptr; }
template <typename Ptr> inline auto qGetPtrHelper(Ptr &ptr) -> decltype(ptr.operator->()) { return ptr.operator->(); }

qGetPtrHelper是一个函数模板重载,用于获取指针。

Qt中d指针与q指针的存在价值
d指针:使用了Pimpl技术,因此Pimpl的优点都是它的价值所在,我认为Qt大量使用该技术主要是为了二进制兼容以及提高编译速度。
q指针:对父类或者公有类方法的访问。

原文链接:https://blog.csdn.net/bmseven/article/details/130245432

篇三:

一、共享d指针实现方法如下: 
1、在基类中定义一个protected权限的d_ptr指针; 3 _2 k6 W5 v2 L1 }
2、在每个派生类中定义d_func(),获取基类d_ptr,并将其转换为当前私有类指针(派生自基类d_ptr); 6 @: r/ a/ {% q
3、在函数中使用Q_D,这样就可以使用d了; . G3 h) _! m9 C1 I7 V) e' t' t
4、在私有数据继承体系中,不要忘记将析构函数定义为虚函数,基类析构函数中释放d_ptr,以防内存泄露!!! n
5、类的派生,加上protected 构造函数,调用父类构造函数,将私有数据类传参; 

二、

此教程中我们从二进制兼容开始,讲到了信息隐藏的技术,介绍了Q_D,Q_Q 等等,其实在window 系统我们经常会用到这种技术,最最典型的的就是processMsg(inttype,long *lParam,long*wParm), 通过lParam、wParam 这两个指针可以传递任何数据类型。

参考原文:Qt信息隐藏(Q_D/Q_Q)介绍_j.&q-CSDN博客      

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1791026.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【深入学习Redis丨第二篇】Redis集群部署详解

文章目录 Redis集群部署Redis4 Cluster部署 Redis集群部署 1 Redis各节点部署 使用源码安装各节点&#xff0c;不过与非cluster方式不同的是&#xff0c;配置文件中需启动cluster相关的配置。 因本次为伪分布式部署&#xff0c;生产环境部署时建议至少3台机器部署&#xff0…

公园【百度之星】/图论+dijkstra

公园 图论dijkstra #include<bits/stdc.h> using namespace std; typedef long long ll; typedef pair<ll,ll> pii; vector<ll> v[40005]; //a、b、c分别是小度、度度熊、终点到各个点的最短距离 ll a[40005],b[40005],c[40005],dist[40005],st[40005]; void…

搭建基于Django的博客系统数据库迁移从Sqlite3到MySQL(四)

上一篇&#xff1a;搭建基于Django的博客系统增加广告轮播图&#xff08;三&#xff09; 下一篇&#xff1a;基于Django的博客系统之用HayStack连接elasticsearch增加搜索功能&#xff08;五&#xff09; Sqlite3数据库迁移到MySQL 数据库 迁移原因 Django 的内置数据库 SQL…

阿里云私有CA使用教程

点击免费生成 根CA详情 启用根CA -----BEGIN CERTIFICATE----- MIIDpzCCAogAwIBAgISBZ2QPcfDqvfI8fqoPkOq6AoMA0GCSqGSIb3DQEBCwUA MFwxCzAJBgNVBAYTAkNOMRAwDgYDVQQIDAdiZWlqaW5nMRAwDgYDVQQHDAdiZWlq aW5nMQ0wCwYDVQQKDARDU0REMQ0wCwYDVQQLDARDU0REMQswCQYDVQQDDAJDTjA…

CAM350如何快速删除Gerber文件上的东西?

文章目录 CAM350如何快速删除Gerber文件上的东西?CAM350如何快速保存已经修改的Gerber文件? CAM350如何快速删除Gerber文件上的东西? CAM如何导入Gerber文件见此篇 今天遇上了一个删除Gerber文件上部分字母的任务&#xff0c;CAM350只能一点点删除线的操作把我手指头差点按…

如何令谷歌浏览器搜索时,子页面使用新窗口,而不是迭代打开

1 问题描述 工作相关需要常用谷歌浏览器&#xff0c;但是现在设置就是每次搜索后&#xff0c;点击搜索结果进去之后&#xff0c;都会覆盖掉原来的父页面&#xff0c;也就是如果我看完了这个子页面的内容&#xff0c;关掉的话&#xff0c;我就需要重新google.com来一遍。。。很…

电路分析答疑 1

三要素法求解的时候&#xff0c; 电容先求U&#xff0c;再利用求导求I 电感先求I&#xff0c;再利用求导求U 若I的头上没有点点&#xff0c;那就是求有效值 叠加定理&#xff0c;不要忘记 若电流值或者电压值已经给出来了&#xff0c;那就说明这一定是直流电。 在画画圈的时候…

【Kubernetes】 emptyDir、nfs存储卷 和 PV、PVC

emptyDir存储卷 当pod被分配给节点 容器和容器之间进行共享存储 hostPath nfs共享存储卷 NAS 专业的存储设备&#xff1b;一般是与NFS 搭配&#xff0c;然后共享出去 GFS 自己搭&#xff1b;CEPH(至少要9台) 第三方&#xff1b;NAS 第三方&#xff1b; 云端 oss …

【多模态】34、LLaVA-v1.5 | 微软开源,用极简框架来实现高效的多模态 LMM 模型

文章目录 一、背景二、方法2.1 提升点2.2 训练样本 三、效果3.1 整体效果对比3.2 模型对于 zero-shot 形式的指令的结果生成能力3.3 模型对于 zero-shot 多语言的能力3.4 限制 四、训练4.1 数据4.2 超参 五、评测六、代码 论文&#xff1a;Improved Baselines with Visual Inst…

深入对比:Transformer与RNN的详细解析

在深度学习领域&#xff0c;特别是在自然语言处理&#xff08;NLP&#xff09;中&#xff0c;循环神经网络&#xff08;RNN&#xff09;和Transformer模型都扮演着举足轻重的角色。然而&#xff0c;随着技术的不断发展&#xff0c;Transformer模型逐渐崭露头角&#xff0c;成为…

量子加速超级计算简介

本文转载自&#xff1a;量子加速超级计算简介(2024年 3月 13日) By Mark Wolf https://developer.nvidia.cn/zh-cn/blog/an-introduction-to-quantum-accelerated-supercomputing/ 文章目录 一、概述二、量子计算机的构建块&#xff1a;QPU 和量子位三、量子计算硬件和算法四、…

回炉重造java----JUC(第二天)

Monitor---监视器/管程 对象头&#xff1a; 操作系统提供的Monitor对象 Synchronized底层实现原理&#xff1a; ①锁对象在加了synchronized之后&#xff0c;对象头中的Mark Word中就存了一个Monitor的地址指针。 ②当一个线程获取到锁之后&#xff0c;Monitor中的Owner属性指…

跳跃游戏二

方法一&#xff1a;&#xff08;双指针法&#xff09;此题参考跳台阶问题&#xff0c;题目要求求到达最后一个点的最小跳跃次数&#xff0c;那么我们就可以从最后一个往前推&#xff0c;先看谁能离得最远&#xff0c;并且能跳到最后一个。假设i位置是离最后一个位置最远&#x…

python字符串的进阶

在上一篇文章的 密码破解器 中&#xff0c;我们回顾了循环专题的知识点。 while 循环和 for 循环是 Python 中的两大循环语句&#xff0c;它们都可以实现循环的功能&#xff0c;但在具体使用时略有差别。当循环次数不确定时&#xff0c;我们选用 while 循环&#xff1b;当循环…

Flutter Bloc之简单记录

目录 0.库安装 1.插件和自动生成 2.状态的配置 1.初始化中&#xff1a; 2.赋值完成后&#xff1a; 3.如果出错&#xff1a; 3.事件的配置 1.定义一个读取事件 2.定义一个更改事件 4.Bloc的设置 5.Bloc的使用 1.BlocProvider 2.内部调用 参考文章进行类的配置 0.库…

RPA实战演练UiBot6.0校园学生教评机器人

前言 校园学生教评机器人&#xff0c;也称为全自动校园教评RPA&#xff08;Robotic Process Automation&#xff0c;机器人流程自动化&#xff09;机器人&#xff0c;是一种利用软件机器人技术来模拟和执行学生教评流程中的各项任务和操作的智能化系统。以下是关于校园学生教评…

【Python绘画】画正方形简笔画

本文收录于 《一起学Python趣味编程》专栏&#xff0c;从零基础开始&#xff0c;分享一些Python编程知识&#xff0c;欢迎关注&#xff0c;谢谢&#xff01; 文章目录 一、前言二、代码示例三、知识点梳理四、总结 一、前言 本文介绍如何使用Python的海龟画图工具turtle&#…

怎么做好企业短信服务呢?(文字短信XML接口示例)

企业短信服务已经成为各行各业都信赖的行业推广方式之一&#xff0c;并且短信行业也与时俱进的发展着&#xff0c;随之而来的就是市场上短信平台的数量也随之增多。那么怎么在鱼龙混杂的短信行业中选择适合自己的企业短信服务平台呢&#xff1f;企业短信服务平台又适用于哪些应…

④单细胞学习-cellchat细胞间通讯

目录 1&#xff0c;原理基础 流程 受体配体概念 方法比较 计算原理 2&#xff0c;数据 3&#xff0c;代码运行 1&#xff0c;原理基础 原文学习Inference and analysis of cell-cell communication using CellChat - PMC (nih.gov) GitHub - sqjin/CellChat: R toolk…

mysql高级刷题-01-求中位数

题目&#xff1a; 解题代码 select sum(num) / count(num) as median from (select num,row_number() over (order by num desc,id desc ) as desc_math,row_number() over (order by num ,id ) as asc_mathfrom number) as t1 where asc_math in (desc_math, desc…