d指针在Qt上的应用及实现(d指针能实现二进制兼容)

Qt为了使其动态库最大程度上实现二进制兼容,引入了d指针的概念。那么为什么d指针能实现二进制兼容呢?为了回答这个问题,首先弄清楚什么是二进制兼容?所谓二进制兼容动态库,指的是一个在老版本库下运行的程序,在不经过编译的情况下,仍然能够在新的版本库下运行;需要经过编译才能在新版本下运行,而不需要修改该程序源代码,我们就说该动态库是源代码兼容的。要使一个dll能达到二进制兼容,对于一个结构,对于一个对象,其数据模型应该不变,若有变动,比如在类中增加数据成员或删除数据成员,其结果肯定影响对象的数据模型,从而导致原有数据程员在对象数据模型里的位移发生变化,这样的话编译后的新版本库很可能使程序发生崩溃,为了使在增加和添加项后不使对象数据模型大小发生变化,一种做法是预先分配若干个保留空间,当要添加项时,使用保留项。如下:

[html] view plain copy

  1. class A {
  2. private:
  3. int a;
  4. int reserved[3];
  5. };
  6. class B {
  7. private:
  8. int a;
  9. quint32 b : 1;
  10. quint32 reserved : 31;
  11. };

这样的话,当样增加项的时候,只需要利用reserved空间,这样的话,对象模型就不会改变。但是这种做法很呆板,因为你不知道未来到底会有多少扩展项,少了不满足要求,多了浪费空间。那麽有没有一种更灵活的方法呢?如下:

[html] view plain copy

  1. class Data {
  2. public:
  3. int a;
  4. };
  5. class A {
  6. private:
  7. Data *d_ptr;
  8. };

将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文件:

[html] view plain copy

  1. QObjectData {
  2. public:
  3. QObject *q_ptr;
  4. ...
  5. };
  6. class Q_CORE_EXPORT QObject
  7. {
  8. ...
  9. Q_DECLARE_PRIVATE(QObject)
  10. public:
  11. Q_INVOKABLE explicit QObject(QObject *parent=0);
  12. virtual ~QObject();
  13. ...
  14. protected:
  15. QObject(QObjectPrivate &dd, QObject *parent = 0);
  16. ...
  17. protected:
  18. QScopedPointer<QObjectData> d_ptr;
  19. ...
  20. };

如上,在这里我算去了其他的项,只保留了于d_ptr有关的项,首先来看看Q_DECLARE_PRIVATE(QObject)是什么:

[html] view plain copy

  1. #define Q_DECLARE_PRIVATE(Class) \
  2. inline Class##Private* d_func() { return reinterpret_cast<Class##Private *>(qGetPtrHelper(d_ptr)); } \
  3. inline const Class##Private* d_func() const { return reinterpret_cast<const Class##Private *>(qGetPtrHelper(d_ptr)); } \
  4. friend class Class##Private;

根据宏定义,则Q_DECLARE_PRIVATE(QObject)翻译如下:

[html] view plain copy

  1. inline QObjectPrivate *d_func()
  2. {
  3. return reinterpret_cast<QObjectPrivate *>(qGetPtrHelper(d_ptr));
  4. }
  5. inline const QObjectPrivate *d_func() const
  6. {
  7. return reinterpret_cast<const QObjectPrivate *>(qGetPtrHelper(d_ptr));
  8. }
  9. friend class QObjectPrivate;

再来看看qGetPtrHelper的定义:

[html] view plain copy

  1. template <typename T> static inline T *qGetPtrHelper(T *ptr)
  2. {
  3. return ptr;
  4. }

再来看QScopePointer,它类似于智能指针,这样不用关心 d_ptr的释放,当离开QScopePointer的作用范围,QScopePointer会自动释放d_ptr指向的堆内存,那麽这个指针是什么时候生成的呢?q_ptr又是什么时候赋值的呢?让我们来看看qobject.cpp的实现:

[html] view plain copy

  1. QObject::QObject(QObject *parent)
  2. : d_ptr(new QObjectPrivate)
  3. {
  4. Q_D(QObject);
  5. d_ptr->q_ptr = this;
  6. ...
  7. }
  8. QObject::QObject(QObjectPrivate &dd, QObject *parent)
  9. : d_ptr(&dd)
  10. {
  11. Q_D(QObject);
  12. d_ptr->q_ptr = this;
  13. ...
  14. }

我们看第一个构造函数,对于根结点的d_ptr指向new QObjectPrivate,而QObjectPrivate派生自QObjectData,那麽Q_D(QObject)宏表示什么意思呢?

[html] view plain copy

  1. #define Q_D(Class) Class##Private * const d = d_func()

Q_D(QObject);翻译如下:

[html] view plain copy

  1. QObjectPrivate * const d = d_func();

不难看出Q_D(QObject);定义了一个QObjectPrivate的常量指针,指向d_func() 的返回值,而该返回值,正是d_ptr(见头文件 d_func()的定义),因此同过Q_D宏我们就可以访问d指针了。

对于第二个构造函数稍后介绍,下面来看看非根结点的d_ptr的实现情况:

头文件:

[html] view plain copy

  1. class Q_CORE_EXPORT QObjectPrivate : public QObjectData
  2. {
  3. Q_DECLARE_PUBLIC(QObject)
  4. ...
  5. };
  6. class Q_GUI_EXPORT QWidgetPrivate : public QObjectPrivate
  7. {
  8. Q_DECLARE_PUBLIC(QWidget)
  9. ...
  10. };
  11. class Q_GUI_EXPORT QWidget : public QObject
  12. {
  13. ...
  14. Q_DECLARE_PRIVATE(QWidget)
  15. ...
  16. public:
  17. ...
  18. explicit QWidget(QWidget* parent = 0, Qt::WindowFlags f = 0);
  19. ...
  20. };

我们首先来看看Q_DECLARE_PUBLIC宏:

[html] view plain copy

  1. #define Q_DECLARE_PUBLIC(Class)                                    \
  2. inline Class* q_func() { return static_cast<Class *>(q_ptr); } \
  3. inline const Class* q_func() const { return static_cast<const Class *>(q_ptr); } \
  4. friend class Class;

根据宏定义,Q_DECLARE_PUBLIC(QObject)翻译如下:

[html] view plain copy

  1. inline QObject *q_func()
  2. {
  3. return static_cast<QObject *>(q_ptr);
  4. }
  5. inline const QObject *q_func() const
  6. {
  7. return static_cast<const QObject *>(q_ptr);
  8. }
  9. friend class QObject;

Q_DECLARE_PUBLIC(QWidget)翻译如下:

[html] view plain copy

  1. inline QWidget *q_func()
  2. {
  3. return static_cast<QWidget *>(q_ptr);
  4. }
  5. inline const QWidget *q_func() const
  6. {
  7. return static_cast<const QWidget *>(q_ptr);
  8. }
  9. friend class QWidget;

注意这里的q_ptr是在QObjectData里公有声明的,QObjectPrivate,QWidgetPrivate都派生或间接派生自QObjectData,所以可以访问q_ptr。

接下来看Q_DECLARE_PRIVATE(QWidget)的翻译:

[html] view plain copy

  1. inline QWidgetPrivate *d_func()
  2. {
  3. return reinterpret_cast<QWidgetPrivate *>(qGetPtrHelper(d_ptr));
  4. }
  5. inline const QWidgetPrivate *d_func() const
  6. {
  7. return reinterpret_cast<const QWidgetPrivate *>(qGetPtrHelper(d_ptr));
  8. }
  9. friend class QWidgetPrivate;

接下来看看QWidget的构造函数的实现:

[html] view plain copy

  1. QWidget::QWidget(QWidget *parent, Qt::WindowFlags f)
  2. : QObject(*new QWidgetPrivate, 0)
  3. {
  4. ...
  5. }

看到QObject(*new QwidgetPrivate, 0)这里调用了QObject的第二个构造函数,将d_ptr指向new QWidgetPrivate所指向的堆内存。

http://blog.csdn.net/rabinsong/article/details/9474859

时间: 2024-10-13 18:29:36

d指针在Qt上的应用及实现(d指针能实现二进制兼容)的相关文章

Qt之美(一):d指针/p指针详解(二进制兼容,不能改变它们的对象布局)

Translated  by  mznewfacer   2011.11.16 首先,看了Xizhi Zhu 的这篇Qt之美(一):D指针/私有实现,对于很多批评不美的同路人,暂且不去评论,只是想支持一下Xizhi Zhu,在引用一下Jerry Sun的话,“C++需要宏定义就像需要设计模式一样.也许你不知道,宏是图灵完全(turing complete)的,至少LISP下是这样,C/C++需要宏,几乎所有重要的C/C++库都需要和依赖宏.这些都超过咱们的想象,宏能带给我们所谓语法糖(Synta

Qt之美(一):d指针/p指针详解(解释二进制兼容,以及没有D指针就会崩溃的例子。有了D指针,所使用的对象大小永远不会改变,它就是该指针的大小。这个指针就被称作D指针)good

Translated  by  mznewfacer   2011.11.16 首先,看了Xizhi Zhu 的这篇Qt之美(一):D指针/私有实现,对于很多批评不美的同路人,暂且不去评论,只是想支持一下Xizhi Zhu,在引用一下Jerry Sun的话,“C++需要宏定义就像需要设计模式一样.也许你不知道,宏是图灵完全(turing complete)的,至少LISP下是这样,C/C++需要宏,几乎所有重要的C/C++库都需要和依赖宏.这些都超过咱们的想象,宏能带给我们所谓语法糖(Synta

C++学习笔记----2.4 C++引用在本质上是什么,它和指针到底有什么区别

从概念上讲.指针从本质上讲就是存放变量地址的一个变量,在逻辑上是独立的,它可以被改变,包括其所指向的地址的改变和其指向的地址中所存放的数据的改变. 而引用是一个别名,它在逻辑上不是独立的,它的存在具有依附性,所以引用必须在一开始就被初始化,而且其引用的对象在其整个生命周期中是不能被改变的(自始至终只能依附于同一个变量). 在C++中,指针和引用经常用于函数的参数传递,然而,指针传递参数和引用传递参数是有本质上的不同的: 指针传递参数本质上是值传递的方式,它所传递的是一个地址值.值传递过程中,被调

指针(pointer) -- (上)

引子: blog对于学开发实在是个好东东,一年前问一大神儿算法问题,大神给了个神秘链接(后来那是他的blog地址),遂点开看是对该算法的解析,举例释义伪代码c实现代码样样齐全,大喜.后来查资料blog是首先搜素的,习惯用搜索引擎时键入问题的关键词 + blog.现在试着写写blog,水平有限都是些比较基础的东西,可能没有多少人需要,不过记录下成长也是极好的嘛,哈哈^_^. 正题: 指针通常难以理解,很多由指针引发的错误往往很有隐蔽性.所以对于指针的理解和使用就需要尤为严谨. 指针(pointer

Xenko基础API笔记3- Pointers指针设备屏幕上点对应的手指触摸。

样本这里是一个简单的示例程序,跟踪目前在屏幕上的指针和打印他们的位置.访问输入字段,类继承自@ SiliconStudio.Xenko.脚本的类. public override async Task Execute() { var pointerPositions = new Dictionary<int, Vector2>(); while (true) { await Scheduler.NextFrame(); foreach (var pointerEvent in Input.Po

c语言数组与指针详解(上)

彻底搞懂c语言数组与指针 部分引用 c语言指针怎么理解 知乎 程序设计入门----c语言 (浙江大学翁恺) <c primer plus>第六版 基础知识 1. 指针基础 &:代表对变量取地址 int*或char*或者把这个星号紧贴着变量比如int *a = &b: 代表新建一个用来储存地址的变量,这也代表&b这个值的类型是int*. int *a, b 或 int* a, b 中只有a是int指针类型,b是int整型. 关于电脑大小端的讨论:大端是指是指数据的高字节,

截图时如何显示鼠标指针,如何带鼠标指针截图,带鼠标指针截图的方法,怎么在截图时把鼠标指针一起截上

截图时如何显示鼠标指针,如何带鼠标指针截图,带鼠标指针截图的方法,怎么在截图时把鼠标指针一起截上 可以借助截图软件来实现, FSCapture(全称FastStone Capture),可是实现截图是带着鼠标指针 HprSnap.可是实现截图是带着鼠标指针 原文地址:https://www.cnblogs.com/GaoNa/p/12304366.html

qt之二进制兼容

一.回顾 使用qt2年多了,但是还是觉得很陌生,总是会被qt搞的很紧张,有时候当我自信满满的打开帮助文档,搜索某个已知的类时,由于笔误敲错了一个字母而出现了另外一个类,不过奇怪的是还真有这么一个类,哎!!!我怎么都不知道呢!看来qt的东西还真不是一般的多,随便一个笔误都可能发现新的东西.特别是qt现在已经发布了qt5.7版本,相对于qt4的时代那真是天差地别,就我个人而言,我现在用的是qt5.6.1-1,因为qt5.6版本官方声称维护2年的时间.qt5.6取消了webkit模块,如果需要使用可以

Qt 中的二进制兼容策略(简而言之就是地址不能变,剩下的就是让地址不变的技巧)

本文翻译自 Policies/Binary Compatibility Issues With C++ 二进制兼容的定义 如果程序从一个以前版本的库动态链接到新版本的库之后,能够继续正常运行,而不需要重新编译,那么我们就说这个库是二进制兼容的. 如果一个程序需要重新编译来运行一个新版本的库,但是不需要对程序的源代码进一步的修改,这个库就是源代码兼容的. 二进制兼容性可以节省很多麻烦.这使得为特定平台分发软件变得更加容易.如果不确保版本之间的二进制兼容性,人们将被迫提供静态链接的二进制文件.静态二