惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Security Latest
Security Latest
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
Stack Overflow Blog
Stack Overflow Blog
WordPress大学
WordPress大学
N
Netflix TechBlog - Medium
GbyAI
GbyAI
云风的 BLOG
云风的 BLOG
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
宝玉的分享
宝玉的分享
博客园 - 【当耐特】
C
Cyber Attacks, Cyber Crime and Cyber Security
雷峰网
雷峰网
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
T
Threat Research - Cisco Blogs
NISL@THU
NISL@THU
Spread Privacy
Spread Privacy
P
Proofpoint News Feed
J
Java Code Geeks
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
MyScale Blog
MyScale Blog
T
Tor Project blog
P
Proofpoint News Feed
C
CERT Recently Published Vulnerability Notes
P
Privacy & Cybersecurity Law Blog
MongoDB | Blog
MongoDB | Blog
Simon Willison's Weblog
Simon Willison's Weblog
C
Cybersecurity and Infrastructure Security Agency CISA
L
LINUX DO - 热门话题
小众软件
小众软件
G
GRAHAM CLULEY
P
Privacy International News Feed
AWS News Blog
AWS News Blog
Know Your Adversary
Know Your Adversary
P
Palo Alto Networks Blog
人人都是产品经理
人人都是产品经理
S
Schneier on Security
Scott Helme
Scott Helme
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
B
Blog RSS Feed
T
The Exploit Database - CXSecurity.com
Recent Announcements
Recent Announcements
E
Exploit-DB.com RSS Feed
C
CXSECURITY Database RSS Feed - CXSecurity.com
U
Unit 42
The Register - Security
The Register - Security
S
Securelist
Martin Fowler
Martin Fowler
Project Zero
Project Zero
大猫的无限游戏
大猫的无限游戏
Cisco Talos Blog
Cisco Talos Blog

博客园 - 鬼谷子com

2026年国内主流AI Coding Plan套餐全对比|开发者避坑指南 图解 | 你管这破玩意儿叫TCP?(转载) C++ static_cast、dynamic_cast、const_cast和reinterpret_cast(四种类型转换运算符) C++中虚函数、虚继承内存模型 c++11新特性实战(二):智能指针 windows下使用mingw和msvc静态编译Qt5.15.xx Qt moc元对象编译器的原理和场景(反射) c++结构体内存对齐 c++11新特性实战 (一):多线程操作 抽象工厂模式(c++实现) 迭代器模式(c++实现) 中介者模式(c++实现) 享元模式(c++实现) 代理模式(c++实现) 状态模式(c++实现) - 鬼谷子com 建造者模式(c++实现) 职责链模式(c++实现) 命令模式(c++实现) 模板方法模式(c++实现)
Qt内部的d指针和q指针手把手教你实现
鬼谷子com · 2021-02-08 · via 博客园 - 鬼谷子com

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>()就可以了。可以收工了~