Qt维基文档翻译:D-指针,D-Pointer
内容目录
什么是d-指针 |
二进制兼容性 — 是什么? |
什么因素会打破二进制兼容性? |
为什么它会崩溃? |
永远不要改变一个已经导出的C++类的尺寸 |
d-指针 |
d-指针所带来的其它好处 |
q-指针 |
继承d-指针,以达到优化目的 |
Qt中的d-指针 |
Q_D和Q_Q |
Q_DECLARE_PRIVATE和Q_DECLARE_PUBLIC |
什么是d-指针
如果 妳曾经阅读过Qt 的源代码,比如说 这个 , 妳会发现, 在其中大规模地使用了 Q_D 和 Q_Q 这两个宏。 本文章,即是为了说明这两个宏的作用。
二进制兼容性 — 是什么?
什么因素会打破二进制兼容性?
回过头来看,对于开发库所做的哪种改变,会使得应用程序必须重新编译才能继续工作呢?下面举个例子:
此处,我们有一个类Widget,其中有一个成员变量geometry。我们编译Widget,并且以版本 WidgetLib 1.0 的形式发布。
对于后续的 WidgetLib 1.1 版本呢,某人想到了一个好点子,让它支持样式单功能。好嘛,我们毫不费力地加上了对应的新的成员方法,以及一个新的 数据成员 。
String m_stylesheet ; // WidgetLib 1.1 中新加入的
我们在做了以上变更之后,发布WidgetLib 1.1。然后,会发现,之前针对WidgetLib 1.0 编译并且正常运行的CuteApp,如今竟然崩溃了!
为什么它会崩溃?
以下图表,简单地表现出了上面那些 简单数据结构 对象在内存中的布局。
Label对象在WidgetLib 1.0中的布局 |
Label对象在WidgetLib 1.1中的布局 |
m_geometry <offset 0> |
m_geometry <offset 0> |
- - - |
m_stylesheet <offset 1> |
m_text <offset 1> |
- - - |
- - - |
m_text <offset 2> |
永远不要改变一个已经导出的C++类的尺寸
d-指针
以下代码展示了此种模式的关键思想(此文章中的所有代码都未提供析构函数,但是在实际代码中,妳应当加上它们)。
/* 由于d_ptr只是 一个指针,并且永远不会在头文件中被引用
(不然会引起编译错误),所以 WidgetPrivate不需要被包含,
或者 写在一个单独的文件中,例如 widget_p.h 。 */
widget_p.h , 即是 widget类的私有头文件。
// 通过这句#include代码,我们就可以访问到WidgetPrivate。
Widget:: Widget () : d_ptr( new WidgetPrivate)
Rect Widget:: geometry () const
// 与WidgetPrivate 不同的是,作者决定直接在源文件中定义LabelPrivate
Label:: Label () : d_ptr( new LabelPrivate)
d-指针所带来的其它好处
•.隐藏实现细节 - 我们在发布WidgetLib 时,只需要发布头文件和二进制文件。那些.cpp文件可以保持闭源状态。
•.头文件中不会暴露出实现细节,因而可以作为应用编程接口参考文档来使用。
•.由于与实现细节相关的头文件被从头文件中移动到了实现(源代码)文件中,所以,编译起来会更快。
当然,相比之下,以上列出的好处确实只是轻微的好处。在Qt 中使用 d-指针的真正原因就是,保持二进制兼容性,并且,Qt 在一开始是闭源软件。
q-指针
WidgetPrivate ( Widget * q ) : q_ptr ( q ) { }
Widget * q_ptr ; // q-ptr指向应用编程接口 类
Widget:: Widget () : d_ptr( new WidgetPrivate( this ))
Rect Widget:: geometry () const
// 与WidgetPrivate 不同的是,作者决定直接在源文件中定义LabelPrivate
LabelPrivate ( Label * q ) : q_ptr ( q ) { }
Label:: Label () : d_ptr( new LabelPrivate( this ))
继承d-指针,以达到优化目的
对于这种问题,解决办法是,让我们的 私有 类组成继承层级关系,并让实际被实例化的类向上逐层传递单个d-指针。
注意,在进行 d-指针的继承时,私有类的声明就必须放置在单独的文件中,例如widget_p.h。再也不能将它声明到widget.cpp 文件中了。
// 允许子类 以自己独有的实体Private类来进行初始化
Widget ( WidgetPrivate & d ) ;
WidgetPrivate ( Widget * q ) : q_ptr ( q ) { } // 构造函数 ,初始化 q-ptr
Widget * q_ptr ; // q-ptr ,指向应用编程接口类
Widget:: Widget () : d_ptr( new WidgetPrivate( this ))
Widget:: Widget (WidgetPrivate &d) : d_ptr( &d)
Label ( LabelPrivate & d ) ; // 允许Label 的子类来传入它们自己独有的 Private 类
// 注意 , Label 并不具有 d_ptr !它直接使用Widget的d_ptr。
class LabelPrivate : public WidgetPrivate
: Widget ( * new LabelPrivate ) // 使用 我们自己独有的Private类来初始化 d-指针
Label:: Label (LabelPrivate &d) : Widget(d)
Qt中的d-指针
注意 ,在Qt 中,所有Private 对象 的基类都是 QObjectPrivate 。
Q_D和Q_Q
我们在之前的步骤中进行的优化,导致了一个副作用,那就是, q-ptr 和 d-ptr 的类型分别是 Widget 和 WidgetPrivate 。 这就意味着,以下代码无法正常工作。
void Label:: setText ( const String &text)
// 无法正常工作 !因为,尽管 d_ptr指向 一个LabelPrivate 对象,但它 的类型是 WidgetPrivate
因此,在从子类中访问时d-指针,我们需要将它静态转换(static_cast)成适当的类型。
void Label:: setText ( const String &text)
LabelPrivate * d = static_cast < LabelPrivate * > ( d_ptr ) ; // 转换 成我们自己的私有类型
如妳所见,如果代码中到处都是static_cast 的话,狠丑。为了解决这个问题,在src/corelib/global/qglobal.h 中定义了两个宏,使得我们可以写出直观的代码:
#define Q_D(Class) Class##Private * const d = d_func()
#define Q_Q(Class) Class * const q = q_func()
// 利用Q_D,妳就可以从Label中使用 LabelPrivate 的成员了
void Label:: setText ( const String &text)
// 利用Q_Q,妳就可以从LabelPrivate中使用 Label的成员了
void LabelPrivate:: someHelperFunction ()
Q_DECLARE_PRIVATE和Q_DECLARE_PUBLIC
对于Qt 类,还 可以 在公有类中使用 Q_DECLARE_PRIVATE 宏。 这个宏的定义如下:
#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));\
Q_DECLARE_PRIVATE ( QLabel ) ;
Qt维基文档翻译:D-指针,D-Pointer相关推荐
- [C++] 什么是智能指针(Smart Pointer)以及何时使用
答案 1 智能指针是一个类,它封装了一个原始的C++指针,以管理所指对象的生命期.没有单一的智能指针类型,但所有这些都尝试以实用的方式抽象原始指针. 智能指针应优先于原始指针. 如果你觉得你需要使用指 ...
- 【C++】智能指针 Smart Pointer
智能指针 智能指针 Smart Pointer auto_ptr 智能指针的自实现 shared_ptr weak_ptr unique_ptr 智能指针 Smart Pointer 用来改善传统指针 ...
- 常量指针(Pointer to const)与指针常量(Const pointer)
常量指针(Pointer to const)与指针常量(Const pointer) 1. Concepts 很多时候对于这两个概念的困惑来自于中文字面翻译.我们无法通过字面含义直接理解判断该'常量' ...
- Qt中的d指针和q指针
概述 如果程序从一个以前版本的库动态链接到新版本的库之后,能够继续正常运行,而不需要重新编译,那么我们就说这个库是二进制兼容的. 如果一个程序需要重新编译来运行一个新版本的库,但是不需要对程序的源代码 ...
- 【软件分析/静态程序分析学习笔记】7.指针分析(Pointer Analysis)入门
写在前面的话 本渣有幸成为南京大学软件学院研究生,在前往仙林校区蹭课的时候偶然发现了这门宝藏课程,听了以后感觉深有收获,但又因为课程难度较大,国庆假期归来发现遗忘较多,因此开了一坑来记录自己对每节课知 ...
- 指针(pointer)与引用(reference)
const const是常量的意思,被其修饰的变量不可修改 如果修饰的是类.结构体(的指针),放在数据类型(int)前后没有区别,其成员也不可以更改 const修饰的是其右边的内容 虽然p1不能修改指 ...
- C 语言编程 — 指针类型 — 指针(pointer)
目录 文章目录 目录 值语义与引用语义 指针的定义 变量指针与指针变量 使用 int* i 还是 int *i ? 取值运算符与取址运算符 指针的种类 双重指针 空指针 悬空指针 野指针 整数指针 指 ...
- QT中的D指针与Q指针
了解QT中引入D指针的初衷前需要了解下二进制兼容 1. 什么是二进制兼容 二进制兼容是针对动态链接库而言的,如果你的源代码已经编译成为可执行文件了,其中你调用了一些动态库,后来你为这个动态库进行了更新 ...
- C++中Reference与指针(Pointer)的使用对比
了解引用reference与指针pointer到底有什么不同可以帮助你决定什么时候该用reference,什么时候该用pointer. 在C++ 中,reference在很多方面与指针(pointer ...
最新文章
- 基于python的文件传输程序_7个步骤,教你快速学会用python实现ftp文件传输功能(收藏了)...
- hive根据职位给员工涨工资_华为28岁员工工资表曝光:厉害的HR,都有这些特质!...
- 艾伟_转载:C#中的委托和事件-抛砖引玉
- spring源码学习一
- mybatisplus修改单个属性_Mybatis Plus 中 参数传递的优化之路
- linux乌班图vi怎么使用,Ubuntu上vi(vim)编辑器使用教程
- python求两数最大公因数_『用python求俩个数的最大公约数和最小公倍数』
- Spring Bean的配置及常用属性
- java 并_java并发编程(一)
- appRTC android studio,如何在android studio模拟器中启用webgl?
- [Swust OJ 632]--集合运算(set容器)
- Nginx学习---Nginx的详解_【all】
- 如果安卓和linux融合,今年手机厂商大吹特吹的内存融合技术,其实十年前网友就玩腻了...
- 狗狗40题~ (Volume C)
- C#合并单元格,AddMergedRegion
- 计算机动画设计与影视制作,计算机动画设计及制作中的特效技术
- office web apps安装部署 Win 2008 安装
- 从浅到深,数据分析人的学习书籍!
- STM32F103ZE 内部flash 读写
- Ubuntu 下面的aMule 的 Kad 或者 Ed2k 连不上
热门文章
- Docker 修改容器名称
- 什么是mysql的索引_什么是MySQL索引?【详解】
- 软考网络管理员学习笔记7之第七章网络管理技术
- 服务器配置RAID5(3块硬盘做RAID5,另外再弄一块做数据冗余盘)
- ltp-ddt qspi_mtd_dd_rw error can't read superblock on /dev/mtdblock0
- POJ 3422 Kaka's Matrix Travels
- Docker入门与应用系列(二)镜像管理
- CodeForces 362B 	Petya and Staircases
- UTC/GMT 时间转换
- Selenium 中文手册