当前位置:   article > 正文

【Qt】d_ptr指针、p_ptr指针详解_qt中的pq指针

qt中的pq指针

此文章可以参考:
Pimpl技术的基本应用
PImpl机制以及Qt的D-Pointer实现
Qt的d_ptr本质上使用了pimp技术

D指针

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

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

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

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.)

Q指针

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

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

进一步优化

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

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

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

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

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

void Label::setText(constString &text) {
    // 不起作用的,因为d_ptr是WidgetPrivate类型的,即使其指向LabelPrivate对象
    d_ptr->text = text;
}
所以为了在子类能够使用d指针,我们用static_cast来做强制转换。

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

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

// global.h (macros)
#define DPTR(Class) Class##Private *d = static_cast<Class##Private *>(d_ptr)
#define QPTR(Class) Class *q = static_cast<Class *>(q_ptr)

// label.cpp
void Label::setText(constString &text) {
   DPTR(Label);
   d->text = text;
}

void LabelPrivate::someHelperFunction() {
   QPTR(label);
   q->selectAll();// 我们现在可以通过此函数来访问所有Label类中的方法
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

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

 //d_ptr.h
#ifndef D_PTR_H
#define D_PTR_H
 
#include <QObject>
 
template <typename T> static inline T *GetPtrHelper(T *ptr) { return ptr; }
 
#define DECLARE_PRIVATE(Class) \
    inline Class##Private* d_func() { return reinterpret_cast<Class##Private*>(GetPtrHelper(d_ptr)); } \
    inline const Class##Private* d_func() const { return reinterpret_cast<const Class##Private*>(GetPtrHelper(d_ptr)); }\
    friend class Class##Private;
 
#define DPTR(Class) Class##Private * const d  = d_func()
 
class MyClassPrivate;
 
class MyClass : public QObject {
    Q_OBJECT
public:
    explicit MyClass(QObject *parent = 0);
    virtual ~MyClass();
    void testFunc();
    protected:
         MyClass(MyClassPrivate &d);
 
private:
        MyClassPrivate * const d_ptr;
    DECLARE_PRIVATE(MyClass);
    MyClass(const MyClass&);
    MyClass& operator= (const MyClass&);
};
 
#endif 


//d_ptr.cpp

#include "d_ptr.h"
#include "q_ptr.h"
 
MyClass::MyClass(QObject *parent) : QObject(parent),
    d_ptr(new MyClassPrivate(this)) {}
 
MyClass::~MyClass() {
    DPTR(MyClass);
    delete d;
}
 
void MyClass::testFunc() {
    DPTR(MyClass);
    d->fool();
}
//q_ptr.h

#ifndef Q_PTR_H
#define Q_PTR_H
 
 
#include <QObject>
#include "d_ptr.h"
 
#define DECLARE_PUBLIC(Class) \
    inline Class* q_func() { return static_cast<Class *>(q_ptr); } \
    inline const Class* q_func() const { return static_cast<const Class *>(q_ptr); } \
    friend class Class;
 
#define QPTR(Class) Class * const q = q_func()
 
class MyClassPrivate : public QObject
{
Q_OBJECT
 
public:
    MyClassPrivate(MyClass *q, QObject *parent = 0);
    virtual ~MyClassPrivate() {}
 
signals:
    void testSgnl();
 
private slots:
    void testSlt();
 
public:
    void fool();
 
private:
    MyClass * const q_ptr;
    DECLARE_PUBLIC(MyClass);
};
 
#endif 


//q_ptr.cpp

#include <stdio.h>
#include "q_ptr.h"
 
MyClassPrivate::MyClassPrivate(MyClass *q, QObject *parent) : QObject(parent), q_ptr(q) {
    connect(this, SIGNAL(testSgnl()), this, SLOT(testSlt()));
}
 
void MyClassPrivate::fool() {
    emit testSgnl();
}
 
void MyClassPrivate::testSlt() {
    printf("This is a pimpl pattern sample implemented in qt's \"d_ptr, q_ptr\" way\n");
}


//main.cpp
#include "q_ptr.h"
 
int main(/*int argc, char *argv[]*/) {
    MyClass * d_ptr = new MyClass;
    d_ptr->testFunc();
    delete d_ptr;
    while(1);
    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122

总结:

  1. QT中的类常用一个PrivateXXX类对象来处理内部逻辑,使得内部逻辑和外部接口分开,这个PrivateXXX对象通过D指针来访问;在PrivateXXX中又需要引用Owner的内容,通过P指针来访问。
  2. 由于D和P指针是从基类继承下来的,子类中由于继承导致类型发生变化,需要通过static_cast类型转换,所以DPTR() 和 QPTR()宏定义实现了转换。
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/正经夜光杯/article/detail/758842
推荐阅读
相关标签
  

闽ICP备14008679号