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. 【Oracle】sum(..) over(..)用法分析

    今天再看sql优化详解的时候,提到了一个sum(..) over(..) 于是自己实验并在网上找了相关的一些文章来看 下面创建一张表: create sequence xulie increment ...

  2. 《进击吧!Blazor!》第一章 2.Hello Blazor

    第二次写专栏,开头还是不知道说什么,所以--先来段广告<进击吧!Blazor!>是本人与张善友老师合作的Blazor零基础入门系列视频,此系列能让一个从未接触过Blazor的程序员掌握开发 ...

  3. MySQL进阶:约束,多表设计,多表查询,视图,数据库备份与还原

    MySQL进阶 知识点梳理 一.约束 1. 外键约束 为什么要有外键约束 例如:一个user表,一个orderlist 如果现在想要直接删除id为1的张三,但是orderlist里还有用户id为1的订 ...

  4. Jmeter接口自动化测试系列之函数使用及扩展

    介绍一下Jmeter自带函数的使用和 函数扩展,来满足测试工作中的各种需求! Jmeter自带函数 点击函数帮助助手图标,弹出函数助手框,可以选择各种各样的函数 举例: _Random 获取随机数,可 ...

  5. jquery 数据查询

    jquery 数据查询 <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> & ...

  6. Linux、JDK、Netty中的NIO与零拷贝

    一.先理解内核空间与用户空间 Linux 按照特权等级,把进程的运行空间分为内核空间和用户空间,分别对应着下图中, CPU 特权等级分为4个,Linux 使用 Ring 0 和 Ring 3. 内核空 ...

  7. Paginator Django 分页 When QuerySets are evaluated QuerySets 执行原理 QuerySets are lazy 惰性执行 访问db取数据的时机

    https://docs.djangoproject.com/en/2.2/topics/pagination/ Paginator objects¶ The Paginator class has ...

  8. java native:Java本地方法调用(jni方式)

    https://www.cnblogs.com/zh1164/p/6283831.html

  9. C++ Primer Plus读书笔记(三)复合类型

    这节主要是介绍数组.结构体.类等概念,以及new delete内存管理. 1.数组 首先普及一下C++中字符串的概念,只有在结尾有 \0 的才叫字符串, cout 输出字符串也以空字符为标记进行结束输 ...

  10. 在Ubuntu安装kubernetes

    一.安装Docker 1. 配置Docker docker安装完成后需要配置cgroup驱动为systemd来增强稳定性 sudo vim /etc/docker/daemon.json { &quo ...