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

2024-06-03 01:04
文章标签 指针 qt 解析 pointer ptr

本文主要是介绍Qt 的 d_ptr (d-pointer) 和 q_ptr (q-pointer)解析;Q_D和Q_Q指针,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

篇一:

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

这篇关于Qt 的 d_ptr (d-pointer) 和 q_ptr (q-pointer)解析;Q_D和Q_Q指针的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1025604

相关文章

C语言中自动与强制转换全解析

《C语言中自动与强制转换全解析》在编写C程序时,类型转换是确保数据正确性和一致性的关键环节,无论是隐式转换还是显式转换,都各有特点和应用场景,本文将详细探讨C语言中的类型转换机制,帮助您更好地理解并在... 目录类型转换的重要性自动类型转换(隐式转换)强制类型转换(显式转换)常见错误与注意事项总结与建议类型

MySQL 缓存机制与架构解析(最新推荐)

《MySQL缓存机制与架构解析(最新推荐)》本文详细介绍了MySQL的缓存机制和整体架构,包括一级缓存(InnoDBBufferPool)和二级缓存(QueryCache),文章还探讨了SQL... 目录一、mysql缓存机制概述二、MySQL整体架构三、SQL查询执行全流程四、MySQL 8.0为何移除查

在Rust中要用Struct和Enum组织数据的原因解析

《在Rust中要用Struct和Enum组织数据的原因解析》在Rust中,Struct和Enum是组织数据的核心工具,Struct用于将相关字段封装为单一实体,便于管理和扩展,Enum用于明确定义所有... 目录为什么在Rust中要用Struct和Enum组织数据?一、使用struct组织数据:将相关字段绑

使用Java实现一个解析CURL脚本小工具

《使用Java实现一个解析CURL脚本小工具》文章介绍了如何使用Java实现一个解析CURL脚本的工具,该工具可以将CURL脚本中的Header解析为KVMap结构,获取URL路径、请求类型,解析UR... 目录使用示例实现原理具体实现CurlParserUtilCurlEntityICurlHandler

深入解析Spring TransactionTemplate 高级用法(示例代码)

《深入解析SpringTransactionTemplate高级用法(示例代码)》TransactionTemplate是Spring框架中一个强大的工具,它允许开发者以编程方式控制事务,通过... 目录1. TransactionTemplate 的核心概念2. 核心接口和类3. TransactionT

数据库使用之union、union all、各种join的用法区别解析

《数据库使用之union、unionall、各种join的用法区别解析》:本文主要介绍SQL中的Union和UnionAll的区别,包括去重与否以及使用时的注意事项,还详细解释了Join关键字,... 目录一、Union 和Union All1、区别:2、注意点:3、具体举例二、Join关键字的区别&php

Spring IOC控制反转的实现解析

《SpringIOC控制反转的实现解析》:本文主要介绍SpringIOC控制反转的实现,IOC是Spring的核心思想之一,它通过将对象的创建、依赖注入和生命周期管理交给容器来实现解耦,使开发者... 目录1. IOC的基本概念1.1 什么是IOC1.2 IOC与DI的关系2. IOC的设计目标3. IOC

java中的HashSet与 == 和 equals的区别示例解析

《java中的HashSet与==和equals的区别示例解析》HashSet是Java中基于哈希表实现的集合类,特点包括:元素唯一、无序和可包含null,本文给大家介绍java中的HashSe... 目录什么是HashSetHashSet 的主要特点是HashSet 的常用方法hasSet存储为啥是无序的

Linux中shell解析脚本的通配符、元字符、转义符说明

《Linux中shell解析脚本的通配符、元字符、转义符说明》:本文主要介绍shell通配符、元字符、转义符以及shell解析脚本的过程,通配符用于路径扩展,元字符用于多命令分割,转义符用于将特殊... 目录一、linux shell通配符(wildcard)二、shell元字符(特殊字符 Meta)三、s

基于Qt Qml实现时间轴组件

《基于QtQml实现时间轴组件》时间轴组件是现代用户界面中常见的元素,用于按时间顺序展示事件,本文主要为大家详细介绍了如何使用Qml实现一个简单的时间轴组件,需要的可以参考下... 目录写在前面效果图组件概述实现细节1. 组件结构2. 属性定义3. 数据模型4. 事件项的添加和排序5. 事件项的渲染如何使用