Translated  by  mznewfacer   2011.11.16

首先,看了Xizhi Zhu 的这篇Qt之美(一):D指针/私有实现,对于很多批评不美的同路人,暂且不去评论,只是想支持一下Xizhi Zhu,在引用一下Jerry Sun的话,“C++需要宏定义就像需要设计模式一样。也许你不知道,宏是图灵完全(turing complete)的,至少LISP下是这样,C/C++需要宏,几乎所有重要的C/C++库都需要和依赖宏。这些都超过咱们的想象,宏能带给我们所谓语法糖(Syntax sugar)的方便。如果你不理解,并且不能熟练使用宏,内联函数和通用模板,那么你还和熟练的C++程序员有一定距离。”
        这里不去评论Jerry Sun的理解,有关宏是否图灵完全,对实际编程也没有啥意义的。至少我们看到Qt用了不少。闲话少叙,书归正文。

1.二进制兼容性

这里,先简单解释一下什么破坏了代码的二进制兼容性(至于二进制兼容性是什么,相信Xizhi Zhu的文章和KDE上的这篇文章,已经说的很清楚了,有时间的话再翻译一下)。换句话说,在对程序做了什么样的改变需要我们重新编译呢?看下面的例子:

  1.  
    class Widget {
  2.  
     
  3.  
    ...
  4.  
     
  5.  
    private:
  6.  
     
  7.  
    Rect m_geometry;
  8.  
     
  9.  
    };
  10.  
     
  11.  
    class Label :public Widget {
  12.  
     
  13.  
    ...
  14.  
     
  15.  
    String text()const{return m_text; }
  16.  
     
  17.  
    private:
  18.  
     
  19.  
    String m_text;
  20.  
     
  21.  
    };
  22.  
     

在这里工程名为CuteApp,Widget类包含一个私有成员变量m_geometry。我们编译Widget类,并且将其发布为WidgetLib 1.0。对于WidgetLib 1.1版本,我们希望加入对样式表的支持。在Widget类中我们相应的加入了新的数据成员。

  1.  
    class Widget {
  2.  
     
  3.  
    ...
  4.  
     
  5.  
    private:
  6.  
     
  7.  
    Rect m_geometry;
  8.  
     
  9.  
    String m_stylesheet; // NEW in WidgetLib 1.1
  10.  
     
  11.  
    };
  12.  
     
  13.  
    class Label :public Widget {
  14.  
     
  15.  
    public:
  16.  
     
  17.  
    ...
  18.  
     
  19.  
    String text()const{return m_text; }
  20.  
     
  21.  
    private:
  22.  
     
  23.  
    String m_text;
  24.  
     
  25.  
    } ;
  26.  
     
  27.  
     

经过上述改变后,我们发现工程CuteApp可以通过编译,但是当运行调用WidgetLib1.0时,程序崩溃。
为什么会运行出错呢?
是因为我们在加入成员变量m_stylesheet后,改变了Widget和Label类的对象布局。这是由于当编译器在编译程序时,它是用所谓的offsets来标记在类中的成员变量。我们将对象布局简化,其在内存中大致形象如下所示:

在WidegetLib 1.0中,Label类的成员变量m_text还在<offset 1>。被编译器编译后,将Label::text()方法解释为获取Label对象的<offset 1>。而在WidegetLib 1.1中,由于添加新的数据成员,导致m_text的标记位变为<offset 2>。由于工程没有重新编译,c++编译器还会将在编译和运行时的对象大小认为一致。也就是说,在编译时,编译器为Label对象按照其大小在内存上分配了空间。而在运行时,由于Widget中m_stylesheet的加入导致Label的构造函数重写了已经存在的内存空间,导致了程序崩溃。

   所以只要版本已发布,除非重新编译工程,否则就不能更改类的结构和大小。那么,为了能够为原有类方便的引入新的功能,这就是Qt引入D指针的目的。

2.D指针

  保持一个库中的所有公有类的大小恒定的问题可以通过单独的私有指针给予解决。这个指针指向一个包含所有数据的私有数据结构体。这个结构体的大小可以随意改变而不会产生副作用,应用程序只使用相关的公有类,所使用的对象大小永远不会改变,它就是该指针的大小。这个指针就被称作D指针。

  1.  
    /* widget.h */
  2.  
    // 私有数据结构体声明。 其定义会在 widget.cpp 或是
  3.  
    // widget_p.h,总之不能在此头文件
  4.  
    class WidgetPrivate;
  5.  
     
  6.  
    class Widget {
  7.  
    ...
  8.  
    Rect geometry()const;
  9.  
    ...
  10.  
    private:
  11.  
    // d指针永远不能在此头文件中被引用
  12.  
    // 由于WidgetPrivate没有在此头文件中被定义,
  13.  
    // 任何访问都会导致编译错误。
  14.  
    WidgetPrivate *d_ptr;
  15.  
    };
  16.  
     
  17.  
    /* widget_p.h */(_p 指示private)
  18.  
    struct WidgetPrivate {
  19.  
    Rect geometry;
  20.  
    String stylesheet;
  21.  
    };
  22.  
     
  23.  
    /* widget.cpp */
  24.  
    #include "widget_p.h"
  25.  
    Widget::Widget()
  26.  
    : d_ptr(new WidgetPrivate)// 初始化 private 数据 {
  27.  
    }
  28.  
     
  29.  
    Rect Widget::geoemtry()const{
  30.  
    // 本类的d指针只能被在自己的库内被访问
  31.  
    return d_ptr->geometry;
  32.  
    }
  33.  
     
  34.  
    /* label.h */
  35.  
    class LabelPrivate;
  36.  
    class Label :publicWidget {
  37.  
    ...
  38.  
    String text();
  39.  
    private:
  40.  
    // 自己类对应自己的d指针
  41.  
    LabelPrivate *d_ptr;
  42.  
    };
  43.  
     
  44.  
    /* label.cpp */
  45.  
    // 这里将私有结构体在cpp中定义
  46.  
    struct LabelPrivate {
  47.  
    String text;
  48.  
    };
  49.  
     
  50.  
    Label::Label()
  51.  
    : d_ptr(new LabelPrivate) {
  52.  
    }
  53.  
     
  54.  
    String Label::text() {
  55.  
    return d_ptr->text;
  56.  
    }

有了上面的结构,CuteApp就不会与d指针直接打交道。因为d指针只能在WidgetLib中被访问,在每一次对Widget修改之后都要对其重新编译,私有的结构体可以随意更改,而不需要重新编译整个工程项目。

3.D指针的其他好处
除了以上优点,d指针还有如下优势:
1.隐藏实现细节——我们可以不提供widget.cpp文件而只提供WidgetLib和相应的头文件和二进制文件。
2.头文件中没有任何实现细节,可以作为API使用。
3.由于原本在头文件的实现部分转移到了源文件,所以编译速度有所提高。
其实以上的点都很细微,自己跟过源代码的人都会了解,qt是隐藏了d指针的管理和核心源的实现。像是在_p.h中部分函数的声明,qt也宣布在以后版本中将会删除。( This file is not part of the Qt API.  It exists purely as an implementation detail.  This header file may change from version to version without notice, or even be removed.)

4.Q指针
到目前为止,我们已经熟悉了指向私有结构体的d指针。而在实际中,往往它将包含私有方法(helper函数)。例如,LabelPrivate可能会有getLinkTargetFromPoint()(helper函数)以当按下鼠标时去找到相应的链接目标。在很多场合,这些helper函数需要访问公有类,例如访问一些属于Label类或是其基类Widget的函数。
比方说,一个帮助函数setTextAndUpdateWidget()可能会调用Widget::update()函数去重新绘制Widget。因此,我们同样需要WidgetPrivate存储一个指向公有类的q指针。

  1.  
    /* widget.h */
  2.  
    class WidgetPrivate;
  3.  
     
  4.  
    class Widget {
  5.  
    ...
  6.  
    Rect geometry()const;
  7.  
    ...
  8.  
    private:
  9.  
    WidgetPrivate *d_ptr;
  10.  
    };
  11.  
     
  12.  
    /* widget_p.h */
  13.  
    struct WidgetPrivate {
  14.  
    // 初始化q指针
  15.  
    WidgetPrivate(Widget *q) : q_ptr(q) { }
  16.  
    Widget *q_ptr;// q-ptr指向基类API
  17.  
    Rect geometry;
  18.  
    String stylesheet;
  19.  
    };
  20.  
     
  21.  
    /* widget.cpp */
  22.  
    #include "widget_p.h"
  23.  
    // 初始化 private 数据,将this指针作为参数传递以初始化 q-ptr指针
  24.  
    Widget::Widget()
  25.  
    : d_ptr(new WidgetPrivate(this)) {
  26.  
    }
  27.  
     
  28.  
    Rect Widget::geoemtry()const{
  29.  
     
  30.  
    return d_ptr->geometry;
  31.  
    }
  32.  
     
  33.  
    /* label.h */
  34.  
    class LabelPrivate;
  35.  
    class Label :publicWidget {
  36.  
    ...
  37.  
    String text()const;
  38.  
    private:
  39.  
    LabelPrivate *d_ptr;};
  40.  
     
  41.  
    /* label.cpp */
  42.  
    struct LabelPrivate {
  43.  
    LabelPrivate(Label *q) : q_ptr(q) { }
  44.  
    Label *q_ptr; //Label中的q指针
  45.  
    String text;
  46.  
    };
  47.  
     
  48.  
    Label::Label()
  49.  
    : d_ptr(new LabelPrivate(this)) {
  50.  
    }
  51.  
     
  52.  
    String Label::text() {
  53.  
    return d_ptr->text;
  54.  
    }

5.进一步优化

在以上代码中,每产生一个Label对象,就会为相应的LabelPrivate和WidgetPrivate分配空间。如果我们用这种方式使用Qt的类,那么当遇到像QListWidget(此类在继承结构上有6层深度),就会为相应的Private结构体分配6次空间。
在下面示例代码中,将会看到,我们用私有类结构去实例化相应构造类,并在其继承体系上全部通过d指针来初始化列表。

  1.  
    /* widget.h */
  2.  
    class Widget {
  3.  
    public:
  4.  
    Widget();
  5.  
    ...
  6.  
    protected:
  7.  
    // 只有子类会访问以下构造函数
  8.  
    Widget(WidgetPrivate &d);// 允许子类通过它们自己的私有结构体来初始化
  9.  
    WidgetPrivate *d_ptr;
  10.  
    };
  11.  
     
  12.  
    /* widget_p.h */
  13.  
    struct WidgetPrivate {
  14.  
    WidgetPrivate(Widget *q) : q_ptr(q) { }
  15.  
    Widget *q_ptr;
  16.  
    Rect geometry;
  17.  
    String stylesheet;
  18.  
    };
  19.  
     
  20.  
    /* widget.cpp */
  21.  
    Widget::Widget()
  22.  
    : d_ptr(new WidgetPrivate(this)) {
  23.  
    }
  24.  
     
  25.  
    Widget::Widget(WidgetPrivate &d)
  26.  
    : d_ptr(&d) {
  27.  
    }
  28.  
     
  29.  
    /* label.h */
  30.  
    class Label :public Widget {
  31.  
    public:
  32.  
    Label();
  33.  
    ...
  34.  
    protected:
  35.  
    Label(LabelPrivate &d);// 允许Label的子类通过它们自己的私有结构体来初始化
  36.  
    // 注意Label在这已经不需要d_ptr指针,它用了其基类的d_ptr
  37.  
    };
  38.  
     
  39.  
    /* label.cpp */
  40.  
    #include "widget_p.h"
  41.  
     
  42.  
    class LabelPrivate :public WidgetPrivate {
  43.  
    public:
  44.  
    String text;
  45.  
    };
  46.  
     
  47.  
    Label::Label()
  48.  
    : Widget(*new LabelPrivate)//用其自身的私有结构体来初始化d指针
  49.  
    }
  50.  
     
  51.  
    Label::Label(LabelPrivate &d)
  52.  
    : Widget(d) {
  53.  
    }

这时候,我觉得我体会到了不一样的感觉,有点意思了吧,说不美的,可以想个更好的解决方案么?
当我们建立一个Label对象时,它就会建立相应的LabelPrivate结构体(其是WidgetPrivate的子类)。它将其d指针传递给Widget的保护构造函数。这时,建立一个Label对象仅需为其私有结构体申请一次内存。Label同样也有一个保护构造函数可以被继承Label的子类使用,以提供自己对应的私有结构体。

6.将q-ptr和d-ptr转换成正确类型

前面一步优化导致的副作用是q-ptr和d-ptr分别是Widget和WidgetPrivate类型。这就意味着下面的操作是不起作用的。

  1.  
    void Label::setText(constString &text) {
  2.  
    // 不起作用的,因为d_ptr是WidgetPrivate类型的,即使其指向LabelPrivate对象
  3.  
    d_ptr->text = text;
  4.  
    }

所以为了在子类能够使用d指针,我们用static_cast来做强制转换。

  1.  
    void Label::setText(const String &text) {
  2.  
    LabelPrivate *d =static_cast<LabelPrivate *>(d_ptr);// cast to our private type
  3.  
    d->text = text;
  4.  
    }
  5.  
     

为了不让所有地方都飘满static_cast,我们才引入宏定义。

  1.  
     
  2.  
     
  3.  
     
  4.  
    // global.h (macros)
  5.  
    #define DPTR(Class) Class##Private *d = static_cast<Class##Private *>(d_ptr)
  6.  
    #define QPTR(Class) Class *q = static_cast<Class *>(q_ptr)
  7.  
     
  8.  
    // label.cpp
  9.  
    void Label::setText(constString &text) {
  10.  
    DPTR(Label);
  11.  
    d->text = text;
  12.  
    }
  13.  
     
  14.  
    void LabelPrivate::someHelperFunction() {
  15.  
    QPTR(label);
  16.  
    q->selectAll();// 我们现在可以通过此函数来访问所有Label类中的方法
  17.  
    }

至于,Qt中的D指针和Q指针的具体形式以及相应的宏定义,这里就不再重复,Xizhi Zhu的文章中已经有写,完整的d指针和q指针的程序实例程序如下:(结合信号和槽机制)

//d_ptr.h

  1.  
    #ifndef D_PTR_H
  2.  
    #define D_PTR_H
  3.  
     
  4.  
    #include <QObject>
  5.  
     
  6.  
    template <typename T> static inline T *GetPtrHelper(T *ptr) { return ptr; }
  7.  
     
  8.  
    #define DECLARE_PRIVATE(Class) \
  9.  
    inline Class##Private* d_func() { return reinterpret_cast<Class##Private*>(GetPtrHelper(d_ptr)); } \
  10.  
    inline const Class##Private* d_func() const { return reinterpret_cast<const Class##Private*>(GetPtrHelper(d_ptr)); }\
  11.  
    friend class Class##Private;
  12.  
     
  13.  
    #define DPTR(Class) Class##Private * const d = d_func()
  14.  
     
  15.  
    class MyClassPrivate;
  16.  
     
  17.  
    class MyClass : public QObject {
  18.  
    Q_OBJECT
  19.  
    public:
  20.  
    explicit MyClass(QObject *parent = 0);
  21.  
    virtual ~MyClass();
  22.  
    void testFunc();
  23.  
    protected:
  24.  
    MyClass(MyClassPrivate &d);
  25.  
     
  26.  
    private:
  27.  
    MyClassPrivate * const d_ptr;
  28.  
    DECLARE_PRIVATE(MyClass);
  29.  
    MyClass(const MyClass&);
  30.  
    MyClass& operator= (const MyClass&);
  31.  
    };
  32.  
     
  33.  
    #endif

//d_ptr.cpp

  1.  
    #include "d_ptr.h"
  2.  
    #include "q_ptr.h"
  3.  
     
  4.  
    MyClass::MyClass(QObject *parent) : QObject(parent),
  5.  
    d_ptr(new MyClassPrivate(this)) {}
  6.  
     
  7.  
    MyClass::~MyClass() {
  8.  
    DPTR(MyClass);
  9.  
    delete d;
  10.  
    }
  11.  
     
  12.  
    void MyClass::testFunc() {
  13.  
    DPTR(MyClass);
  14.  
    d->fool();
  15.  
    }

//q_ptr.h

  1.  
    #ifndef Q_PTR_H
  2.  
    #define Q_PTR_H
  3.  
     
  4.  
     
  5.  
    #include <QObject>
  6.  
    #include "d_ptr.h"
  7.  
     
  8.  
    #define DECLARE_PUBLIC(Class) \
  9.  
    inline Class* q_func() { return static_cast<Class *>(q_ptr); } \
  10.  
    inline const Class* q_func() const { return static_cast<const Class *>(q_ptr); } \
  11.  
    friend class Class;
  12.  
     
  13.  
    #define QPTR(Class) Class * const q = q_func()
  14.  
     
  15.  
    class MyClassPrivate : public QObject
  16.  
    {
  17.  
    Q_OBJECT
  18.  
     
  19.  
    public:
  20.  
    MyClassPrivate(MyClass *q, QObject *parent = 0);
  21.  
    virtual ~MyClassPrivate() {}
  22.  
     
  23.  
    signals:
  24.  
    void testSgnl();
  25.  
     
  26.  
    private slots:
  27.  
    void testSlt();
  28.  
     
  29.  
    public:
  30.  
    void fool();
  31.  
     
  32.  
    private:
  33.  
    MyClass * const q_ptr;
  34.  
    DECLARE_PUBLIC(MyClass);
  35.  
    };
  36.  
     
  37.  
    #endif

//q_ptr.cpp

  1.  
    #include <stdio.h>
  2.  
    #include "q_ptr.h"
  3.  
     
  4.  
    MyClassPrivate::MyClassPrivate(MyClass *q, QObject *parent) : QObject(parent), q_ptr(q) {
  5.  
    connect(this, SIGNAL(testSgnl()), this, SLOT(testSlt()));
  6.  
    }
  7.  
     
  8.  
    void MyClassPrivate::fool() {
  9.  
    emit testSgnl();
  10.  
    }
  11.  
     
  12.  
    void MyClassPrivate::testSlt() {
  13.  
    printf("This is a pimpl pattern sample implemented in qt's \"d_ptr, q_ptr\" way\n");
  14.  
    }

//main.cpp

  1.  
    #include "q_ptr.h"
  2.  
     
  3.  
    int main(/*int argc, char *argv[]*/) {
  4.  
    MyClass * d_ptr = new MyClass;
  5.  
    d_ptr->testFunc();
  6.  
    delete d_ptr;
  7.  
    while(1);
  8.  
    return 0;
  9.  
    }

https://blog.csdn.net/mznewfacer/article/details/6976293

Qt之美(一):d指针/p指针详解(解释二进制兼容,以及没有D指针就会崩溃的例子。有了D指针,所使用的对象大小永远不会改变,它就是该指针的大小。这个指针就被称作D指针)good的更多相关文章

  1. Qt之美(一):d指针/p指针详解(二进制兼容,不能改变它们的对象布局)

    Translated  by  mznewfacer   2011.11.16 首先,看了Xizhi Zhu 的这篇Qt之美(一):D指针/私有实现,对于很多批评不美的同路人,暂且不去评论,只是想支持 ...

  2. 【Qt】Qt Quick 之 QML 与 C++ 混合编程详解

    Qt Quick 之 QML 与 C++ 混合编程详解 - CSDN博客   专栏:Qt Quick简明教程 - CSDN博客   .

  3. C++中指针与引用详解

    在计算机存储数据时必须要知道三个基本要素:信息存储在何处?存储的值为多少?存储的值是什么类型?因此指针是表示信息在内存中存储地址的一类特殊变量,指针和其所指向的变量就像是一个硬币的两面.指针一直都是学 ...

  4. C++中this指针的用法详解

    转自 http://blog.chinaunix.net/uid-21411227-id-1826942.html 1. this指针的用处: 一个对象的this指针并不是对象本身的一部分,不会影响s ...

  5. C++中this指针的用法详解(转)

    原文地址:http://blog.chinaunix.net/uid-21411227-id-1826942.html 1. this指针的用处: 一个对象的this指针并不是对象本身的一部分,不会影 ...

  6. [转载] C++中this指针的用法详解

    摘自:http://blog.chinaunix.net/uid-21411227-id-1826942.html 1. this指针的用处: 一个对象的this指针并不是对象本身的一部分,不会影响s ...

  7. Android智能指针sp wp详解

    研究Android的时候,经常会遇到sp.wp的东西,网上一搜,原来是android封装了c++中对象回收机制.说明:1. 如果一个类想使用智能指针,那么必须满足下面两个条件:    a. 该类是虚基 ...

  8. C/C++数组名与指针的区别详解

    1.数组名不是指针我们看下面的示例: #include <iostream> int main() { ]; char *pStr = str; cout << sizeof( ...

  9. 【转】C++中this指针的用法详解

    1.this指针的用处 一个对象的this指针并不是对象本身的一部分,不会影响sizeof(对象)的结果.this作用域是在类内部,当在类的非静态成员函数中访问类的非静态成员的时候,编译器会自动将对象 ...

随机推荐

  1. js中两种定时器的设置及清除

    1.循环执行: var timeid = window.setInterval(“方法名或方法”,“延时”); window.clearInterval(timeid); <script typ ...

  2. C#面试题及答案 一 <转来的,貌似有看评论说有错误,正在一个个纠正中…… 也望园友们指出>

    1. 简述 private. protected. public. internal 修饰符的访问权限. 答 . private  私有成员, 在类的内部才可以访问.  protected  保护成员 ...

  3. java多线程系列(二)---对象变量并发访问

    对象变量的并发访问 前言:本系列将从零开始讲解java多线程相关的技术,内容参考于<java多线程核心技术>与<java并发编程实战>等相关资料,希望站在巨人的肩膀上,再通过我 ...

  4. PowerDesigner中翻转生成PDM图时把Name属性变成注释(转)

    在pd里面运行下面这段代码'******************************************************************************'* File: ...

  5. 接口自动化学习--mock

    好久没有写学习的总结,都正月十二了,但还是要来个新年快乐鸭. 一直都在看imooc的一套java接口自动化实战课程,现在看到了尾部了,然后想到之前那些testng,mock,httpclient等都没 ...

  6. tomcat启动项目的时候不报错而且启动的很快

    最后发现是tomcat部署项目的时候,并没有将一部分文件复制到tomcat的目录下 方法 将没有添加的目录 Finish

  7. sqli-labs学习笔记 DAY6

    DAY 6 sqli-labs lesson 30 与上一题一样,需要用到HPP 查看源代码,参数两边加上了双引号,直接使用lesson 26a与lesson 27a的脚本即可 sqli-labs l ...

  8. Vue.js 相关知识(组件)

    1. 组件介绍 组件(component),vue.js最强大的功能之一 作用:封装可重用的代码,通常一个组件就是一个功能体,便于在多个地方都能调用该功能体 根组件:我们实例化的Vue对象就是一个组件 ...

  9. js最简单的动画

    $(document).ready(function(){ //�ֶ�����ҳ��Ԫ�� $("#reset").click(function(){ $("*" ...

  10. CSS命名规范(规则)常用的CSS命名规则

    CSS命名规范(规则)常用的CSS命名规则   CSS命名规范(规则)常用的CSS命名规则   头:header   内容:content/container   尾:footer ...