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

篇一:

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,就可以直接使用qemit 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_Qdelete 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_OBJECTQ_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_ptrd_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.mzph.cn/bicheng/23058.shtml

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

相关文章

这才是大模型价格战背后的真相

想必大家今天肯定被各家大模型厂商的降价新闻刷圈了&#xff0c;如果说 Meta Llama 3 的开源是国外大模型市场的搅局者&#xff0c;那 DeepSeek-V2 就是国内大模型市场的鲶鱼&#xff0c;但是价格战背后是大模型基础设施优化带来的物美价廉&#xff0c;还是浑水摸鱼的噱头&…

引擎:Shader

一、原理 创建Shader脚本&#xff0c;创建材质球&#xff0c;将物体的渲染效果Shader脚本挂载到材质球&#xff0c;最后把材质球挂到3d物体上面从而实现渲染。 二、模型边缘发光 原理&#xff1a;正对着摄像机的模型三角面边缘光最弱&#xff0c;垂直于摄像机的模型三角面边缘光…

解锁下载EasyRecovery2024电脑版软件 3步破解下载秘籍!

在数字时代&#xff0c;数据已成为我们生活中不可或缺的一部分。无论是工作中的重要文件&#xff0c;还是珍贵的家庭照片和视频&#xff0c;数据都承载着我们的回忆和努力。然而&#xff0c;数据的丢失也是我们常常遇到的问题。硬盘损坏、误删除、病毒攻击等都可能导致数据丢失…

Nodejs 第七十四章(微服务)

什么是微服务&#xff1f; micro servers 微服务和微前端是类似的&#xff0c;微前端就是借鉴了微服务的理念去实现的&#xff0c;那么微服务指的就是&#xff0c;将应用程序拆分成为一系列小型、独立的服务&#xff0c;每个服务都是专注于执行特定的业务&#xff0c;比如文章…

第N4周:中文文本分类

&#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 一、预备知识 中文文本分类和英文文本分类都是文本分类&#xff0c;为什么要单独拎出来个中文文本分类呢&#xff1f; 在自然语言处理&#xff08;NLP&#x…

Tomcat相关概述和部署

目录 一、Tomcat知识 1.Tomcat概述 2.Tomcat组件构成 3.Tomcat 功能组件结构 4.Tomcat的请求过程 二、tomcat服务部署 1.老样子准备工作——关闭防火墙和selinux&#xff0c;防止其对安装过程的干扰 2.将准备好的软件包拖入/opt目录下&#xff0c;进行安装JDK 3.设置J…

嵌入式学习记录6.5(内存分配/构造函数/析构函数)

目录 目录 一.c动态内存分配回收 1.1分配 1.2回收 1.3new、delete和malloc、free之间的区别(重点&#xff09; 二.构造函数 2.1功能,格式 2.2示例 三.析构函数 3.1功能&#xff0c;格式 3.2特点 3.3示例 四.思维导图/练习 4.1思维导图 4.2练习 一.c动态内存分配回…

无需复杂步骤,Win11用户轻松开启旧版文件资源管理器!

在Win11电脑操作中&#xff0c;用户可以使用到新版的文件资源管理器&#xff0c;但总是有各种错误、卡顿等问题的出现&#xff0c;所以很多用户都不喜欢新版资源管理器。接下来小编给大家介绍一个简单的方法&#xff0c;帮助Win11用户快速开启旧版文件资源管理器。 具体操作如下…

【RISC-V】站在巨人的肩膀上——看开源芯片、软件生态、与先进计算/人工智能/安全的结合

目录 会议议程专题二&#xff1a;RISC-V与先进计算基于RISC-V的后量子密码芯片设计&#xff0c;刘冬生&#xff0c;华中科技大学存算一体集成芯片&#xff0c;刘琦&#xff0c;复旦大学面向端侧大模型计算的RISC-V矩阵扩展架构&#xff0c;复旦大学&#xff0c;韩 军 专题五&am…

开源基于Rust编写的Web服务器

基于 RUST 的 WEB 资源服务器 Github 地址 LTPP-GIT 地址 官方文档 该项目于 2024 年 5 月 1 日开始开发 预期功能 功能支持情况当前情况多线程支持是是服务支持配置化是是防盗链支持是是gzip 支持是是反向代理支持是是自定义状态码对应资源文件是是日志支持是是负载均衡支…

easyexcel模板填充列表

引入依赖 <dependency><groupId>com.alibaba</groupId><artifactId>easyexcel</artifactId><version>2.2.6</version></dependency>编写模板 编写代码 public class FillData {private String name;private Double number;pu…

如何解决 Zabbix模板同步超时:解决运维技术领域的BugFailed to sync Zabbix template due to timeout

如何解决 Zabbix模板同步超时&#xff1a;解决运维技术领域的BugFailed to sync Zabbix template due to timeout 原创作者&#xff1a; 猫头虎 作者微信号&#xff1a; Libin9iOak 作者公众号&#xff1a; 猫头虎技术团队 更新日期&#xff1a; 2024年6月6日 博主猫头虎…

LabVIEW源程序安全性保护综合方案

LabVIEW源程序安全性保护综合方案 一、硬件加密保护方案 选择和安装硬件设备 选择加密狗和TPM设备&#xff1a;选择Sentinel HASP加密狗和支持TPM&#xff08;可信平台模块&#xff09;的计算机主板。 安装驱动和开发工具&#xff1a;安装Sentinel HASP加密狗的驱动程序和开发…

Java物业管理系统+数据库应用程序开发[JavaSE+JDBC+idea控制台+MySQL]

背景&#xff1a; 使用JavaSEJDBCMySQL技术实现一个物业管理系统&#xff0c;具体要求如下 物业管理系统需求&#xff1a; 需求分析 1.1用户需求分析 在进入系统之前&#xff0c;要进行身份确认&#xff0c;只有用户名和用户密码都相符的用户方可进入本系统&#xff0c;为…

SASAM软件架构静态分析法-系统架构师(六)

1、体系结构权衡分析法&#xff08;Architecture Tradeoff Analysis Method ATAM&#xff09;包含四个主要活动领域&#xff0c;分别是 场景和需求的收集、体系结构视图和场景的实现、&#xff08;&#xff09;、折中。基于场景的架构分析方法&#xff08;Scenarios-based Arch…

大归纳!!教你使用<string.h>的字符函数与字符串函数!!☑

这篇博客为你归纳了所有的字符函数和最常用的字符串函数&#xff0c;以及对应的模拟实现&#xff01;&#xff01;你可以直接循着目录跳到你需要的段落哦&#xff01;&#xff01;&#x1f60d; 目录 字符函数 字符分类 字符判断函数 islower——判断小写字母 isupper——…

JVM学习-Jprofiler

JProfiler 基本概述 特点 使用方便&#xff0c;界面操作友好对被分析的应用影响小(提供模板)CPU&#xff0c;Tread&#xff0c;Memory分析功能尤其强大支持对jdbc,noSql,jsp,servlet,socket进行分析支持多种模式(离线、在线)的分析支持监控本地、远程JVM跨平台&#xff0c;拥…

纯血鸿蒙开发教程:如何实现运动饮食卡片效果

开发背景 人们对健康的要求越来越高&#xff0c;从单纯的健康饮食到健康运动&#xff0c;再到两者的结合。但是&#xff0c;饮食和运动之间的平衡一般人很难掌握&#xff0c;而我们这款 APP 将饮食、运动、以及自身身体状况&#xff08;如体脂、体重、内脂等&#xff09;有机结…

linux上VirtualBox使用

前言 最近想把唯一的windows系统装成linux&#xff0c; 但是确实存在一些特殊软件无法舍弃&#xff0c;所有装完linux需要用虚拟机装个windows 上来使用特定的一些软件&#xff08;不想用wine了&#xff09;。 还有对一些特定usb设备的透传&#xff0c;这样才能保证在虚拟机中…

【Linux】Linux工具——gdb

1. gdb 概述 GDB是GNU开源组织发布的一个强大的UNIX下的程序调试工具。或许&#xff0c;各位比较喜欢那种图形界面方式的&#xff0c;像VC、BCB等IDE的调试&#xff0c;但如果你是在 UNIX平台下做软件&#xff0c;你会发现GDB这个调试工具有比VC、BCB的图形化调试器更强大的功能…