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对象的构造,空间成本浪费,我们要进行针对性的改造。

    1. 我们是不是可以复用基类里面的d_ptr
    2. 这样的话我们要去掉子类里面的d_ptr
    3. 我们要用WidgetPrivate* ->LabelPrivate *就要使用c++多态的性质,所以要构造必要条件
      1. 继承
      2. 虚函数
  • 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:
    }

此版本包含几个修改点:

  1. 公开类添加一个protected级别的构造函数,用于子类构造的时候在初始化参数列表来初始化基类,这里实现了多态的特性。
  2. 公开类的子类构造函数的初始化参数列表不再初始化d_ptr,而是调用基类的带参构造函数,实参为*new LabelPrivate,跟[1]配合实现了多态性。
  3. d指针转换成子类型的私有类才都调用相关的方法。
  4. Label子类也要实现一个protected保护级别的构造函数,因为Label也可能会被继承。
  5. WidgetPrivate.h私有基类的析构函数定义为virtual,这样在释放资源的时候才能够不漏掉LabelPrivate的释放。
  6. 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指针手把手教你实现的更多相关文章

  1. Qt编程之d指针与q指针

    我们在Qt中可以看到两个宏Q_D和Q_Q这两个红分别是取得d指针和q指针的,d指针指向封装的私有类,q指针指向公共的类.(我的理解类似于回调,回指的意思). 为什么Qt要这样实现呢?下面几个链接中的文 ...

  2. [转]QT中的D指针与Q指针

    Qt为了使其动态库最大程度上实现二进制兼容,引入了d指针的概念. 那么为什么d指针能实现二进制兼容呢? 为了回答这个问题,首先弄清楚什么是二进制兼容? 所谓二进制兼容动态库,指的是一个在老版本库下运行 ...

  3. Qt中的Q_D宏和d指针

    _ZTS7QObject 一.Q_D的在文件中的提法 Q_D的设置意在方便地获取私有类指针,文件为qglobal.h.下面的##是宏定义的连字符.假设类名是A,那么A##Private翻译过来就是AP ...

  4. Qt中的ui指针和this指针

    初学qt,对其ui指针和this指针产生疑问,画了个把小时终于搞懂了. 首先看ui指针的定义: 在mainwindow.h中 private: Ui::MainWindow *ui; Ui又是什么? ...

  5. 关于空指针NULL、野指针、通用指针 (转)

    reference:https://www.cnblogs.com/losesea/archive/2012/11/16/2772590.html 首先说一下什么是指针,只要明白了指针的含义,你就明白 ...

  6. C++中的指针、数组指针与指针数组、函数指针与指针函数

    C++中的指针.数组指针与指针数组.函数指针与指针函数 本文从刚開始学习的人的角度,深入浅出地具体解释什么是指针.怎样使用指针.怎样定义指针.怎样定义数组指针和函数指针.并给出相应的实例演示.接着,差 ...

  7. NULL指针、零指针、野指针

    1.1.空指针 如果 p 是一个指针变量,则 p = 0; p = 0L; p = '\0'; p = 3 - 3; p = 0 * 17;p=(void*)0; 中的任何一种赋值操作之后, p 都成 ...

  8. c/c++ 复习基础要点01-const指针、指针函数 函数指针、new/delete与malloc/free区别与联系

    1.      引用本身是有指针实现的:引用为只读指针 例子: int d=123; int& e=d;    //引用 int * const e=d; //只读指针,e指向d,不可修改e指 ...

  9. Android系统的智能指针(轻量级指针、强指针和弱指针)的实现原理分析

    文章转载至CSDN社区罗升阳的安卓之旅,原文地址:http://blog.csdn.net/luoshengyang/article/details/6786239 Android 系统的运行时库层代 ...

随机推荐

  1. 【Linux】添加硬盘不需要重启服务器

    添加硬盘之后,不用重启服务器 执行下面的语句 ls /sys/class/scsi_host 查看下面有多少host 我这里有三个host 分别执行 echo "- - -" &g ...

  2. 分布式系统:dubbo的连接机制

    目录 研究这个问题的起因 dubbo的连接机制 为什么这么做 dubbo同步转异步 dubbo的实现 纯netty的简单实现 总结 研究这个问题的起因 起因是一次面试,一次面试某电商网站,前面问到缓存 ...

  3. Kubernetes 开船记-脚踏两只船:用 master 服务器镜像克隆出新集群

    自从2020年2月23日 园子全站登船 之后,我们一边感叹"不上船不知道,一上船吓一跳" -- kubernetes 比 docker swarm 强大太多,一边有一个杞人忧天的担 ...

  4. spring boot 集成 websocket 实现消息主动

    来源:https://www.cnblogs.com/leigepython/p/11058902.html pom.xml 1 <?xml version="1.0" en ...

  5. 前端知识(二)05-Eslint语法规范检查-谷粒学院

    目录 一.ESLint简介 二.启用ESLint 1.ESLint插件安装 2.插件的扩展设置 3.确认开启语法检查 三.ESLint规则说明 1.规则说明 2.语法规则 一.ESLint简介 ESL ...

  6. Django的数据库读写分离

    Django的数据库读写分离 1.首先是配置数据库 在settings.py文件中增加多个数据库的配置: DATABASES = { 'default': { 'ENGINE': 'django.db ...

  7. 1.Spring的基本应用

    1.1概述 1.1.1 Spring是什么 Spring一个轻量级的框架,以IOC(控制反转)和AOP(面向切面编程)为内核,Spring在表现层提供了Spring MVC的框架整和功能,在业务逻辑层 ...

  8. WIFI 国家码和信道划分

    前言 网上百度了很多资料,都没有找到国家码对应支持哪些信道的资料,无奈只能qiang到谷歌,分享给大家完整的WIFI 国家码和信道划分. 安卓WIFI国家码的影响 android中设置wifi国家码的 ...

  9. 如何将python中pip源设置为国内源

    1.Windows Python的学习过程中,往往会学习到很多库,而安装各类库的时候,往往不尽人意,下载速度从几KB到十几KB.甚至下载到一半还超时报错.这都是因为pip源是访问国外的官方源,如果需要 ...

  10. .net core 不同地区时间相互转换

    .net core 不同地区时间相互转换 //韩国时间转换成当前时间 //value=需要转换的时间 //Korea Standard Tim 韩国时间 //China Standard Time 中 ...