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. Xshell与Xftp免费下载安装及步骤

    Xshell与Xftp免费下载安装及步骤 1.进入Xshell的官网:https://www.netsarang.com/zh/ 加粗样式 2.选择你需要的软件进行下载如:Xshell 3.选择家庭和 ...

  2. 【Java】面向对象

    重新搞一波 复习巩固 简单记录 慕课网 imooc Java 零基础入门-Java面向对象-面向对象 都是视频课件里的. 文章目录 面向对象 什么是对象 什么是面向对象 类 什么是对象的属性和方法 类 ...

  3. LeetCode653. 两数之和 IV - 输入 BST

    题目 直接暴力 1 class Solution { 2 public: 3 vector<int>ans; 4 bool findTarget(TreeNode* root, int k ...

  4. Sgu149 Computer Network

    Sgu149 Computer Network 题目描述 给你一棵N(N<=10000)个节点的树,求每个点到其他点的最大距离. 不难想到一个节点到其他点的最大距离为:max(以它为根的子树的最 ...

  5. 技术基础 | Apache Cassandra 4.0基准测试

    Apache Cassandra 4.0已经发布了Beta版,这是第一个支持JDK 11及更高JDK版本的Cassandra版本.   时延对于Apache Cassandra用户来说是个显而易见的关 ...

  6. JVM(四)打破双亲委派和SPI机制

    前言: 我们都知道判断两个类是不是同一个,要根据类加载器和全限定名.这是为什么呢?为什么不同的类加载器加载同一个类是不同的呢? 答案就是,不同的类加载器所加载的类在方法区的存储空间是不同的即Insta ...

  7. 安装OpenDaylight及Openflow插件

    1. 安装 Java 和 Maven CentOS7: yum install java-1.8.0-openjdk.x86_64 java-1.8.0-openjdk-devel.x86_64 ma ...

  8. Python学习【第3篇】:列表魔法

    ##########################深灰魔法-list类中提供的方法###################list 类,列表list = [1,12,9,"age" ...

  9. 算法总结篇---AC自动机

    目录 写在前面 算法流程 引例: 概述: Trie树的构建(第一步) 失配指针(第二步) 构建失配指针 字典树和字典图 多模式匹配 例题 写在前面 鸣谢: OiWiki 「笔记」AC 自动机---Lu ...

  10. DP 从棺材到入土

    区间DP P1063 能量项链 题目描述 给定一串首尾相连的能量珠串 按照该计算规则进行合并:如果前一颗能量珠的头标记为\(m\),尾标记为\(r\),后一颗能量珠的头标记为\(r\),尾标记为\(n ...