Qt内部的d指针和q指针手把手教你实现
Qt内部的d指针和q指针
在讲Qt的D指针之前让我们来简单的解释一下D指针出现的目的,目的是什么呢?保证模块间的二进制兼容。
什么是二进制兼容呢,简单说就是如果自己的程序使用了第三方模块,二进制兼容可以保证在修改了第三方模块之后,也就是已经改变了内存布局之后,自己的程序可以不用重新编译就能够兼容修改后的第三方模块。 二进制指的是编译生成的.so或者dll库,一旦程序编译好之后类的内存布局就确定了,兼容性值得就是即使内存布局被改变也依然能够通过原来的内存布局找到对应的成员变量,比较官方的解释是:
二进制兼容:在升级库文件的时候,不必重新编译使用此库的可执行文件或其他库文件,并且程序的功能不被破坏。
一些详细的介绍可以参考这个博文。
之前转载的一篇文章介绍了多态场景下基类和子类的内存布局。
当我们知道二进制兼容这个问题存在之后,在没有参考Qt等解决方案之前,用我们自己聪明的小脑袋瓜子想一想怎么解决这个问题?调整私有成员变量的顺序会造成二进制不兼容,那我们把一个类的私有成员单独拿出来封装成一个只有成员变量的类,然后用一个指针指向这个类对象是不是就可以了呢?很好,这种解决问题的思路很好,比直接百度不思考要强多了。
马斯克为什么这么厉害,我记得在一个采访中他提到了为什么自己这么牛逼,什么事情都敢干,回答是因为自己坚信第一性原理这个理论。简单阐述就是:
如果一件事从理论上是可行的,那就可以去干
那我们得到启发之后回到这个问题本身,已经有了对二进制兼容的定义,我们根据上面的分析得出结论,用指针的方式实现不就可以规避二进制不兼容的情况了吗?我们先动手尝试完成一个自己脑子里的第一版实现:
- widget.h
class WidgetPrivate;
class Widget
{
public:
Widget():d_ptr(new WidgetPrivate){ d_ptr->q_ptr = this; }//1
~Widget(){ if(d_ptr) delete d_ptr; }
inline void setWidth(int width) { d_ptr->width = width; }
inline int getWidth() { return d_ptr->width; }
protected:
WidgetPrivate* d_ptr = nullptr;
}
- widgetPrivate.h
class Widget;
class WidgetPrivate
{
public:
WidgetPrivate(){}
~WidgetPrivate(){}
int width;
int height;
Widget* q_ptr;
}
1处的代码可以直接设置q指针的方式比较优雅,不然我们要修改widgetPrivate(Widget* widget): q_ptr(widget){}为这样的构造函数,使用的时候
Widget():d_ptr(new WidgetPrivate(this)),显然这种方式不够优雅。这样的话classPrivate类就看着非常干净,甚至把class替换成struct都可以~
总的来说看起来很完美,widgetPrivate作为私有类我们在改动的时候并不会破坏widget的二进制兼容性。然后呢,够了吗?我们知道Qt的GUI类是对象树的结构,存在着多层次继承结构(QObject<-QWidget<-QLabel ...),也在此基础上实现了内存半自动化管理的机制。我们如果加一个子类呢?动手试试
Label.h
class LabelPrivate;
class Label:public Widget
{
public:
Lable():d_ptr(new LabelPrivate){ d_ptr->q_ptr = this; }
~Lable(){ if(d_ptr) delete d_ptr; } inline void setIcon(std::string icon){ d_ptr->icon = icon; }
inline std::string getIcon() { return d_ptr->icon; }
protected:
LabelPrivate* d_ptr = nullptr;
}
LabelPrivate.h
class Label;
class LabelPrivate
{
public:
LabelPrivate(){}
~LabelPrivate(){} std::string icon;
Label* q_ptr = nullptr;
private:
}
使用
Label label;
label.setWidth(65);
label.setHeight(100);
label.setIcon("d:/image/prettyGirl");
我们把构造函数的过程打印出来
WidgetPrivate::WidgetPrivate()
Widget::Widget()
WidgetPrivate::WidgetPrivate()
LabelPrivate::LabelPrivate()
Label::Label()
我们可以看到
WidgetPrivate::WidgetPrivate()构造函数被调用了两次,因为子类Label也有一个d_ptr指针。这还是只有一层继承结构的时候,每多一层继承结构都要平白无故的增添一个BaseClassPrivate对象的构造,空间成本浪费,我们要进行针对性的改造。- 我们是不是可以复用基类里面的d_ptr
- 这样的话我们要去掉子类里面的d_ptr
- 我们要用WidgetPrivate* ->LabelPrivate *就要使用c++多态的性质,所以要构造必要条件
- 继承
- 虚函数
Widget.h
class WidgetPrivate;
class Widget
{
public:
Widget():d_ptr(new WidgetPrivate){ d_ptr->q_ptr = this; }
~Widget(){ if(d_ptr) delete d_ptr; } inline void setWidth(int width) {
WidgetPrivate* d = reinterpret_cast<WidgetPrivate*>(d_ptr);
d->width = width;
}
inline int getWidth() {
WidgetPrivate* d = reinterpret_cast<WidgetPrivate*>(d_ptr);
return d->width;
} protected:
Widget(WidgetPrivate& wprivate):d_ptr(&wprivate){}//[1]
WidgetPrivate* d_ptr = nullptr;
}
Label.h
class LabelPrivate;
class Label:public Widget
{
public:
Lable():Widget(*new LabelPrivate){ d_ptr->q_ptr = this; }//[2]
~Lable(){ if(d_ptr) delete d_ptr; } inline void setIcon(std::string icon){
LabelPrivate* d = reinterpret_cast<LabelPrivate*>(d_ptr);//[3]
d_ptr->icon = icon;
}
inline std::string getIcon() {
LabelPrivate* d = reinterpret_cast<LabelPrivate*>(d_ptr);
return d_ptr->icon;
}
protected:
Label(LabelPrivate& lprivate):d_ptr(&lprivate){}//[4]
}
WidgetPrivate.h
class Widget;
class WidgetPrivate
{
public:
WidgetPrivate(){}
virtual ~WidgetPrivate(){}//[5] int width;
int height;
Widget* q_ptr;
}
LabelPrivate.h
class Label;
class LabelPrivate:public WidgetPrivate//[6]
{
public:
LabelPrivate(){}
~LabelPrivate(){} std::string icon;
Label* q_ptr = nullptr;
private:
}
此版本包含几个修改点:
- 公开类添加一个protected级别的构造函数,用于子类构造的时候在初始化参数列表来初始化基类,这里实现了多态的特性。
- 公开类的子类构造函数的初始化参数列表不再初始化d_ptr,而是调用基类的带参构造函数,实参为*new LabelPrivate,跟[1]配合实现了多态性。
- d指针转换成子类型的私有类才都调用相关的方法。
- Label子类也要实现一个protected保护级别的构造函数,因为Label也可能会被继承。
- WidgetPrivate.h私有基类的析构函数定义为virtual,这样在释放资源的时候才能够不漏掉LabelPrivate的释放。
- LabelPrivate继承WidgetPrivate,构成多态的基础条件。
Ok,到这里就基本完成了,可以不做修改的替换掉Qt的那一套d指针和q指针,哈哈哈(有点扯了。)论实用程度是够了,但是论优雅程度跟Qt原生的还是有一定距离,我们添加一些语法糖和c++11的智能指针来优化一下。
global.h
#define D_D(Class) Class##Private* d = reinterpret_cast<Class##Private*>(d_ptr.get());
#define Q_D(Class) Class* q = reinterpret_cast<Class*>(d_ptr.get());
label.h
class LabelPrivate;
class Label:public Widget
{
public:
Lable():Widget(*new LabelPrivate){ d_ptr->q_ptr = this; }
~Lable(){ if(d_ptr) delete d_ptr; } inline void setIcon(std::string icon){
D_D(Label)//[1]
d->icon = icon;
}
inline std::string getIcon() {
D_D(Label)//[2]
return d->icon;
}
protected:
Label(LabelPrivate& lprivate):d_ptr(&lprivate){}
}
是不是优雅了很多.
智能指针的话只需要替换Widget::d_ptr为
std::unique_ptr<Widget>()就可以了。可以收工了~
Qt内部的d指针和q指针手把手教你实现的更多相关文章
- Qt编程之d指针与q指针
我们在Qt中可以看到两个宏Q_D和Q_Q这两个红分别是取得d指针和q指针的,d指针指向封装的私有类,q指针指向公共的类.(我的理解类似于回调,回指的意思). 为什么Qt要这样实现呢?下面几个链接中的文 ...
- [转]QT中的D指针与Q指针
Qt为了使其动态库最大程度上实现二进制兼容,引入了d指针的概念. 那么为什么d指针能实现二进制兼容呢? 为了回答这个问题,首先弄清楚什么是二进制兼容? 所谓二进制兼容动态库,指的是一个在老版本库下运行 ...
- Qt中的Q_D宏和d指针
_ZTS7QObject 一.Q_D的在文件中的提法 Q_D的设置意在方便地获取私有类指针,文件为qglobal.h.下面的##是宏定义的连字符.假设类名是A,那么A##Private翻译过来就是AP ...
- Qt中的ui指针和this指针
初学qt,对其ui指针和this指针产生疑问,画了个把小时终于搞懂了. 首先看ui指针的定义: 在mainwindow.h中 private: Ui::MainWindow *ui; Ui又是什么? ...
- 关于空指针NULL、野指针、通用指针 (转)
reference:https://www.cnblogs.com/losesea/archive/2012/11/16/2772590.html 首先说一下什么是指针,只要明白了指针的含义,你就明白 ...
- C++中的指针、数组指针与指针数组、函数指针与指针函数
C++中的指针.数组指针与指针数组.函数指针与指针函数 本文从刚開始学习的人的角度,深入浅出地具体解释什么是指针.怎样使用指针.怎样定义指针.怎样定义数组指针和函数指针.并给出相应的实例演示.接着,差 ...
- NULL指针、零指针、野指针
1.1.空指针 如果 p 是一个指针变量,则 p = 0; p = 0L; p = '\0'; p = 3 - 3; p = 0 * 17;p=(void*)0; 中的任何一种赋值操作之后, p 都成 ...
- c/c++ 复习基础要点01-const指针、指针函数 函数指针、new/delete与malloc/free区别与联系
1. 引用本身是有指针实现的:引用为只读指针 例子: int d=123; int& e=d; //引用 int * const e=d; //只读指针,e指向d,不可修改e指 ...
- Android系统的智能指针(轻量级指针、强指针和弱指针)的实现原理分析
文章转载至CSDN社区罗升阳的安卓之旅,原文地址:http://blog.csdn.net/luoshengyang/article/details/6786239 Android 系统的运行时库层代 ...
随机推荐
- 【Docker】在Linux系统中安装Docker虚拟机、启动停止重启查看Docker命令
安装Docker 文章目录 安装Docker Docker虚拟机架构 在Linux系统中安装Docker虚拟机 管理Docker虚拟机 参考资料 Docker虚拟机架构 Docker创建的所有虚拟实例 ...
- 浅入深出了解XXE漏洞
环境搭建 https://github.com/c0ny1/xxe-lab 为了更深入的理解,我准备理论和实际相结合的了解XXE! 浅谈XML 初识XML 一个好的代码基础能帮助你更好理解一类漏洞,所 ...
- 三十三:WEB漏洞-逻辑越权之水平垂直越权
水平和垂直越权 水平越权:可以获得同级别用户权限 垂直权限:享受高几个层次的用户权限 解释,原理,检测,利用,防御 通过更换的某个ID之类的身份标识,从而使得A账号获取(修改,删除)B账号的数据,通过 ...
- jackson学习之一:基本信息
欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...
- Docker容器日志清理方案
Docker容器在运行过程中会产生很多日志,久而久之,磁盘空间就被占满了,以下分享docker容器日志清理的几种方法 删除日志 在linux上,容器日志一般存放在 /var/lib/docker/co ...
- 编码占用的字节数 1 byte 8 bit 1 sh 1 bit 中文字符编码 2. 字符与编码在程序中的实现 变长编码 Unicode UTF-8 转换 在网络上传输 保存到磁盘上 bytes
小结: 1.UNICODE 字符集编码的标准有很多种,比如:UTF-8, UTF-7, UTF-16, UnicodeLittle, UnicodeBig 等: 2 服务器->网页 utf-8 ...
- 某商城系统(V1.3-2020-01-10)前台命令执行漏洞
漏洞文件: ./inc/module/upload_img.php 先跟进 del_file 函数: 在 del_file 函数中首先执行了unlink操作,然后接着进行了file_exists 判断 ...
- 框架spring+strutrs+ibatis
Tomcat加载完成 --- Web.xml --- sql-map-config.xml --- 读取xml(*-ibatis-config) --- Jsp的url --- action方法 -- ...
- 前端api管理工具YApi
使用YApi接口管理工具,提升前端开发效率前端开发苦恼: 代码中使用json数据模拟后端api数据,注释调取api接口代码,代码乱七八糟 为了测试不同case,央求后端人员返回不同的数据,返回状态.返 ...
- Django(图书管理系统)
图书管理系统 注意事项 1.models 要创建好,规划好自己的表,以及各种表关系 2.url正则要写好 3.settings的配置 4.利用bootstarp 进行布局更漂亮哦 5.注意orm 各 ...