d指针在Qt上的应用及实现(d指针能实现二进制兼容)
Qt为了使其动态库最大程度上实现二进制兼容,引入了d指针的概念。那么为什么d指针能实现二进制兼容呢?为了回答这个问题,首先弄清楚什么是二进制兼容?所谓二进制兼容动态库,指的是一个在老版本库下运行的程序,在不经过编译的情况下,仍然能够在新的版本库下运行;需要经过编译才能在新版本下运行,而不需要修改该程序源代码,我们就说该动态库是源代码兼容的。要使一个dll能达到二进制兼容,对于一个结构,对于一个对象,其数据模型应该不变,若有变动,比如在类中增加数据成员或删除数据成员,其结果肯定影响对象的数据模型,从而导致原有数据程员在对象数据模型里的位移发生变化,这样的话编译后的新版本库很可能使程序发生崩溃,为了使在增加和添加项后不使对象数据模型大小发生变化,一种做法是预先分配若干个保留空间,当要添加项时,使用保留项。如下:
- class A {
- private:
- int a;
- int reserved[3];
- };
- class B {
- private:
- int a;
- quint32 b : 1;
- quint32 reserved : 31;
- };
这样的话,当样增加项的时候,只需要利用reserved空间,这样的话,对象模型就不会改变。但是这种做法很呆板,因为你不知道未来到底会有多少扩展项,少了不满足要求,多了浪费空间。那麽有没有一种更灵活的方法呢?如下:
- class Data {
- public:
- int a;
- };
- class A {
- private:
- Data *d_ptr;
- };
将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文件:
- QObjectData {
- public:
- QObject *q_ptr;
- ...
- };
- class Q_CORE_EXPORT QObject
- {
- ...
- Q_DECLARE_PRIVATE(QObject)
- public:
- Q_INVOKABLE explicit QObject(QObject *parent=0);
- virtual ~QObject();
- ...
- protected:
- QObject(QObjectPrivate &dd, QObject *parent = 0);
- ...
- protected:
- QScopedPointer<QObjectData> d_ptr;
- ...
- };
如上,在这里我算去了其他的项,只保留了于d_ptr有关的项,首先来看看Q_DECLARE_PRIVATE(QObject)是什么:
- #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;
根据宏定义,则Q_DECLARE_PRIVATE(QObject)翻译如下:
- inline QObjectPrivate *d_func()
- {
- return reinterpret_cast<QObjectPrivate *>(qGetPtrHelper(d_ptr));
- }
- inline const QObjectPrivate *d_func() const
- {
- return reinterpret_cast<const QObjectPrivate *>(qGetPtrHelper(d_ptr));
- }
- friend class QObjectPrivate;
再来看看qGetPtrHelper的定义:
- template <typename T> static inline T *qGetPtrHelper(T *ptr)
- {
- return ptr;
- }
再来看QScopePointer,它类似于智能指针,这样不用关心 d_ptr的释放,当离开QScopePointer的作用范围,QScopePointer会自动释放d_ptr指向的堆内存,那麽这个指针是什么时候生成的呢?q_ptr又是什么时候赋值的呢?让我们来看看qobject.cpp的实现:
- QObject::QObject(QObject *parent)
- : d_ptr(new QObjectPrivate)
- {
- Q_D(QObject);
- d_ptr->q_ptr = this;
- ...
- }
- QObject::QObject(QObjectPrivate &dd, QObject *parent)
- : d_ptr(&dd)
- {
- Q_D(QObject);
- d_ptr->q_ptr = this;
- ...
- }
我们看第一个构造函数,对于根结点的d_ptr指向new QObjectPrivate,而QObjectPrivate派生自QObjectData,那麽Q_D(QObject)宏表示什么意思呢?
- #define Q_D(Class) Class##Private * const d = d_func()
Q_D(QObject);翻译如下:
- QObjectPrivate * const d = d_func();
不难看出Q_D(QObject);定义了一个QObjectPrivate的常量指针,指向d_func() 的返回值,而该返回值,正是d_ptr(见头文件 d_func()的定义),因此同过Q_D宏我们就可以访问d指针了。
对于第二个构造函数稍后介绍,下面来看看非根结点的d_ptr的实现情况:
头文件:
- class Q_CORE_EXPORT QObjectPrivate : public QObjectData
- {
- Q_DECLARE_PUBLIC(QObject)
- ...
- };
- class Q_GUI_EXPORT QWidgetPrivate : public QObjectPrivate
- {
- Q_DECLARE_PUBLIC(QWidget)
- ...
- };
- class Q_GUI_EXPORT QWidget : public QObject
- {
- ...
- Q_DECLARE_PRIVATE(QWidget)
- ...
- public:
- ...
- explicit QWidget(QWidget* parent = 0, Qt::WindowFlags f = 0);
- ...
- };
我们首先来看看Q_DECLARE_PUBLIC宏:
- #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;
根据宏定义,Q_DECLARE_PUBLIC(QObject)翻译如下:
- inline QObject *q_func()
- {
- return static_cast<QObject *>(q_ptr);
- }
- inline const QObject *q_func() const
- {
- return static_cast<const QObject *>(q_ptr);
- }
- friend class QObject;
Q_DECLARE_PUBLIC(QWidget)翻译如下:
- inline QWidget *q_func()
- {
- return static_cast<QWidget *>(q_ptr);
- }
- inline const QWidget *q_func() const
- {
- return static_cast<const QWidget *>(q_ptr);
- }
- friend class QWidget;
注意这里的q_ptr是在QObjectData里公有声明的,QObjectPrivate,QWidgetPrivate都派生或间接派生自QObjectData,所以可以访问q_ptr。
接下来看Q_DECLARE_PRIVATE(QWidget)的翻译:
- inline QWidgetPrivate *d_func()
- {
- return reinterpret_cast<QWidgetPrivate *>(qGetPtrHelper(d_ptr));
- }
- inline const QWidgetPrivate *d_func() const
- {
- return reinterpret_cast<const QWidgetPrivate *>(qGetPtrHelper(d_ptr));
- }
- friend class QWidgetPrivate;
接下来看看QWidget的构造函数的实现:
- QWidget::QWidget(QWidget *parent, Qt::WindowFlags f)
- : QObject(*new QWidgetPrivate, 0)
- {
- ...
- }
看到QObject(*new QwidgetPrivate, 0)这里调用了QObject的第二个构造函数,将d_ptr指向new QWidgetPrivate所指向的堆内存。
http://blog.csdn.net/rabinsong/article/details/9474859
d指针在Qt上的应用及实现(d指针能实现二进制兼容)的更多相关文章
- d指针在Qt上的应用及实现
Qt为了使其动态库最大程度上实现二进制兼容,引入了d指针的概念.那么为什么d指针能实现二进制兼容呢?为了回答这个问题,首先弄清楚什么是二进制兼容?所谓二进制兼容动态库,指的是一个在老版本库下运行的程序 ...
- d指针在Qt上的应用及实现(有图,很清楚)
Qt为了使其动态库最大程度上实现二进制兼容,引入了d指针的概念.那么为什么d指针能实现二进制兼容呢?为了回答这个问题,首先弄清楚什么是二进制兼容?所谓二进制兼容动态库,指的是一个在老版本库下运行的程序 ...
- Qt之美(一):d指针/p指针详解(解释二进制兼容,以及没有D指针就会崩溃的例子。有了D指针,所使用的对象大小永远不会改变,它就是该指针的大小。这个指针就被称作D指针)good
Translated by mznewfacer 2011.11.16 首先,看了Xizhi Zhu 的这篇Qt之美(一):D指针/私有实现,对于很多批评不美的同路人,暂且不去评论,只是想支持 ...
- Qt之美(一):d指针/p指针详解(二进制兼容,不能改变它们的对象布局)
Translated by mznewfacer 2011.11.16 首先,看了Xizhi Zhu 的这篇Qt之美(一):D指针/私有实现,对于很多批评不美的同路人,暂且不去评论,只是想支持 ...
- Xenko基础API笔记3- Pointers指针设备屏幕上点对应的手指触摸。
样本这里是一个简单的示例程序,跟踪目前在屏幕上的指针和打印他们的位置.访问输入字段,类继承自@ SiliconStudio.Xenko.脚本的类. public override async Task ...
- Qt上FFTW組件的编译与安裝
Qt上FFTW組件的編譯安裝 FFTW是一個做頻譜非常實用的組件,本文講述在Windows和Linux兩個平臺使用FFTW組件.Windows下的的FFTW組件已經編譯好成爲dll文件,按照開發應用的 ...
- C++学习笔记----3.2 C++引用在本质上是什么,它和指针到底有什么区别
从概念上讲.指针从本质上讲就是存放变量地址的一个变量,在逻辑上是独立的,它可以被改变,包括其所指向的地址的改变和其指向的地址中所存放的数据的改变. 而引用是一个别名,它在逻辑上不是独立的,它的存在具有 ...
- Qt之二进制兼容
一.回顾 使用qt2年多了,但是还是觉得很陌生,总是会被qt搞的很紧张,有时候当我自信满满的打开帮助文档,搜索某个已知的类时,由于笔误敲错了一个字母而出现了另外一个类,不过奇怪的是还真有这么一个类,哎 ...
- Qt 中的二进制兼容策略(简而言之就是地址不能变,剩下的就是让地址不变的技巧)
本文翻译自 Policies/Binary Compatibility Issues With C++ 二进制兼容的定义 如果程序从一个以前版本的库动态链接到新版本的库之后,能够继续正常运行,而不需要 ...
随机推荐
- 制作与使用静态链接库(.lib)文件
(一)制作.lib文件 (1)打开vs,选择“新建项目”,选择“Visual C++“,选择”Win32 控制台应用程序“. (2)点击”确定“,点击”下一步“,设置如下 (3)点击”完成“,然后就可 ...
- 并发编程: c++11 thread(Func, Args...)利用类成员函数创建线程
c++11是VS2012后支持的新标准,为并发编程提供了方便的std::thread. 使用示例: #include <thread> void thread_func(int arg1, ...
- Linux学习之常用技巧
▌基础 学习 Bash .你可以man bash来看看bash的东西,并不复杂也并不长.你用别的shell也行,但是bash是很强大的并且也是系统默认的.(学习zsh或tsch只会让你在很多情况下受到 ...
- hadoop笔记之MapReduce的应用案例(利用MapReduce进行排序)
MapReduce的应用案例(利用MapReduce进行排序) MapReduce的应用案例(利用MapReduce进行排序) 思路: Reduce之后直接进行结果合并 具体样例: 程序名:Sort. ...
- 解决eclipse创建Maven项目后无法生成src/main/java资源文件夹的方法
在项目上右键选择properties,然后点击java build path,在Librarys下,编辑JRE System Library,选择workspace default jre.
- windows下python2和python3共存
相信很多朋友都在网上搜索过python多版本共存的问题. 多说的说法都是修改python.exe的名字为python2.exe或者python3.exe. 但是我按照这样的方法却总是不成功. 修改py ...
- c语言题目:找出一个二维数组的“鞍点”,即该位置上的元素在该行上最大,在该列上最小。也可能没有鞍点
//题目:找出一个二维数组的“鞍点”,即该位置上的元素在该行上最大,在该列上最小.也可能没有鞍点. // #include "stdio.h" #include <stdli ...
- cocos2d-x -------之笔记篇 环境的安装
cocos2d-x -------之笔记篇 环境的安装 使用到的工具有VS2010 cygwin android-NDK eclipse android SDK 1.首先是android相关环境的安 ...
- MVC4,MVC3,VS2012+ entity framework Migration from Sqlserver to Mysql
在开发的初期个人认为因VS与Sqlserver的配合很默契,即可以方便的实现Code First,又可以使用SqlServer Manager很漂亮的进行建模与变更,也许是个人的使用习惯MS的界面做的 ...
- Html5离线应用程序
最近,整理了一下关于 H5离线应用缓存的知识,今天在家休息,和大家分享一下,希望对大的学习和工作,能有所帮助. HTML5的离线web应用允许我们在脱机时与网站进行交互.这在提高网站的访问速度和制作一 ...