`
bd2007
  • 浏览: 395016 次
  • 性别: Icon_minigender_2
  • 来自: 上海
社区版块
存档分类
最新评论
阅读更多

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

[size=18px;]1.二进制兼容性[/size]

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

<textarea readonly name="code" class="cpp">class Widget {

...

private:

Rect m_geometry;

};

class  Label :public Widget {

...

String text()const{return m_text; }

private:

String m_text;

};

</textarea>[size=18px;]在这里工程名为CuteApp,Widget类包含一个私有成员变量<span style="text-align: left;">m_geometry[/size]<span style="text-align: left;">。我们编译</span><span style="text-align: left;">Widget</span><span style="text-align: left;">类,并且将其发布为</span>WidgetLib 1.0。<span style="margin: 0px; width: auto!important; float: none!important; height: auto!important; vertical-align: baseline!important;">对于WidgetLib
1.1版本,我们希望加入对样式表的支持。在Widget类中我们相应的加入了新的数据成员。</span></span>

<textarea readonly name="code" class="cpp">class  Widget {

...

private:

Rect m_geometry;

String m_stylesheet; // NEW in WidgetLib 1.1

};

class  Label :public Widget {

public:

...

String text()const{return m_text; }

private:

String m_text;

} ;

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

<code class="cpp plain" style="margin: 0px; width: auto!important; font-family: Consolas,'Bitstream Vera Sans Mono','Courier New',Courier,monospace!important; float: none!important; height: auto!important; vertical-align: baseline!important;"><span style="font-family: Consolas,'Bitstream Vera Sans Mono','Courier New',Courier,monospace;"><span style="font-family: Consolas,'Bitstream Vera Sans Mono','Courier New',Courier,monospace; line-height: 14px;"><span style="color: #363534;"><img alt="" src="http://hi.csdn.net/attachment/201111/16/0_1321421628H7dW.gif"></span></span></span></code>

<code class="cpp plain" style="margin: 0px; width: auto!important; font-family: Consolas,'Bitstream Vera Sans Mono','Courier New',Courier,monospace!important; float: none!important; height: auto!important; vertical-align: baseline!important;">[size=18px;]在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的构造函数重写了已经存在的内存空间,导致了程序崩溃。[/size]</code>

<code class="cpp plain" style="margin: 0px; width: auto!important; font-family: Consolas,'Bitstream Vera Sans Mono','Courier New',Courier,monospace!important; float: none!important; height: auto!important; vertical-align: baseline!important;">[size=18px;]
所以只要版本已发布,除非重新编译工程,否则就不能更改类的结构和大小。那么,为了能够为原有类方便的引入新的功能,这就是Qt引入D指针的目的。[/size]</code>

<code class="cpp plain" style="margin: 0px; width: auto!important; font-family: Consolas,'Bitstream Vera Sans Mono','Courier New',Courier,monospace!important; float: none!important; height: auto!important; vertical-align: baseline!important;"></code><code class="cpp plain" style="margin: 0px; width: auto!important; font-family: Consolas,'Bitstream Vera Sans Mono','Courier New',Courier,monospace!important; float: none!important; height: auto!important; vertical-align: baseline!important;">[size=18px;]2.D指针[/size]</code>

[size=18px;]<code class="cpp plain" style="margin: 0px; width: auto!important; font-family: Consolas,'Bitstream Vera Sans Mono','Courier New',Courier,monospace!important; float: none!important; height: auto!important; vertical-align: baseline!important;"></strong></code>保持一个库中的所有公有类的大小恒定的问题可以通过单独的私有指针给予解决。这个指针指向一个包含所有数据的私有数据结构体。这个结构体的大小可以随意改变而不会产生副作用,应用程序只使用相关的公有类,所使用的对象大小永远不会改变,它就是该指针的大小。这个指针就被称作D指针。[/size]

[size=18px;]</span><textarea readonly name="code" class="cpp">/* 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;
}
</textarea><br>

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

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

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



<textarea readonly name="code" class="cpp">/* 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;
}</textarea><br>

[size=18px;]5.进一步优化[/size]

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



<textarea readonly name="code" class="cpp">/* widget.h */
class  Widget {
public:
   Widget();
    ...
protected:
   // 只有子类会访问以下构造函数
   Widget(WidgetPrivate &amp;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 &amp;d)
  : d_ptr(&amp;d) {
}

/* label.h */
class Label :public Widget {
public:
   Label();
    ...
protected:
   Label(LabelPrivate &amp;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 &amp;d)
   : Widget(d) {
}</textarea>[size=18px;]这时候,我觉得我体会到了不一样的感觉,有点意思了吧,说不美的,可以想个更好的解决方案么?<br>
当我们建立一个Label对象时,它就会建立相应的LabelPrivate结构体(其是WidgetPrivate的子类)。它将其d指针传递给Widget的保护构造函数。这时,建立一个Label对象仅需为其私有结构体申请一次内存。Label同样也有一个保护构造函数可以被继承Label的子类使用,以提供自己对应的私有结构体。[/size]

[size=18px;]6.将q-ptr和d-ptr转换成正确类型[/size]

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

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

<textarea readonly name="code" class="cpp">void Label::setText(const String &amp;text) {
    LabelPrivate *d =static_cast<LabelPrivate *>(d_ptr);// cast to our private type
    d->text = text;
}

</textarea>[size=18px;]为了不让所有地方都飘满static_cast,我们才引入宏定义。[/size]

<textarea readonly name="code" class="cpp">


// 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 &amp;text) {
    DPTR(Label);
    d->text = text;
}

void LabelPrivate::someHelperFunction() {
    QPTR(label);
    q->selectAll();// 我们现在可以通过此函数来访问所有Label类中的方法
}</textarea>[size=18px;]至于,Qt中的D指针和Q指针的具体形式以及相应的宏定义,这里就不再重复,<span>Xizhi Zhu的文章中已经有写,完整的d指针和q指针的程序实例程序如下:(结合信号和槽机制)[/size][/size]

[size=18px;]<span>[/size]</span>
[size=18px;]//d_ptr.h[/size]

<textarea readonly name="code" class="cpp">#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 &amp;d);

private:
MyClassPrivate * const d_ptr;
    DECLARE_PRIVATE(MyClass);
    MyClass(const MyClass&amp;);
    MyClass&amp; operator= (const MyClass&amp;);
};

#endif
</textarea>[size=18px;]//d_ptr.cpp<br>[/size]

<textarea readonly name="code" class="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();
}</textarea><strong>[size=18px;]//q_ptr.h[/size][/b]

<textarea readonly name="code" class="cpp">#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 </textarea><br>
//q_ptr.cpp

<textarea readonly name="code" class="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");
}
</textarea><br>
//main.cpp

<textarea readonly name="code" class="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;
}
</textarea><br><br><br><br>

 
0
0
分享到:
评论

相关推荐

    QT编程技术详解.pptx

    ### QT编程技术详解 #### 一、QT介绍 ##### 1.1 跨平台特性 - **概述**:Qt 是一款强大的跨平台应用程序和用户界面(UI)开发框架,能够帮助开发者实现一次开发多平台部署的目标。这意味着开发者只需要编写一次源...

    qt试题.doc

    【知识点详解】 1. QT试题概述: QT是一个流行的开源跨平台应用程序开发框架,由Qt Company维护,主要用于构建图形用户界面(GUI)应用。QT支持多种编程语言,包括C++,并提供了一种称为Qt Quick(QML)的声明式...

    详解C++中的this指针

    ### 详解C++中的this指针 #### 一、引言 在C++编程语言中,`this`指针是一个非常重要的概念,尤其在面向对象编程中扮演着关键角色。`this`指针是一个指向当前对象的指针,它允许程序员在类的成员函数内部引用该对象...

    详解QT内存泄露问题

    ### 详解QT内存泄露问题 在探讨QT内存管理机制及如何避免内存泄露之前,我们首先应当理解何为内存泄露。内存泄露是指程序中已分配的堆内存由于未能释放,导致一直占用这部分内存空间,最终可能导致应用程序运行缓慢...

    dxf嵌入式QT进阶.7z

    1. **Qt模块详解**: - **Qt Widgets**:这是Qt的核心模块,提供了各种GUI控件,如按钮、文本框等,用于构建用户界面。 - **Qt Quick**(QML):这是一个声明式UI设计语言,允许开发者创建动态、现代的用户界面,...

    从基础开始的Qt学习路线

    ### Qt学习路线详解 #### 一、C++基础学习阶段(6-8周) ##### 1. C++语言入门 - **程序结构**: - **第一个程序**:`#include &lt;iostream&gt;` 引入标准输入输出库。`using namespace std;` 使程序能够直接使用`std...

    Qt智能指针--QWeakPointer.pdf

    【Qt智能指针QWeakPointer详解】 在C++编程中,智能指针是管理动态分配对象生命周期的关键工具。Qt框架提供了两种智能指针类型:QSharedPointer和QWeakPointer。QSharedPointer是一个强引用智能指针,它负责跟踪所...

    QT旋钮自我实现方法

    ### QT旋钮自我实现方法详解 #### 一、引言 在GUI开发中,旋钮是一种常见的用户界面元素,常用于调节数值或控制设备。本文将详细介绍如何在QT4环境下,利用C++语言实现自定义的旋钮功能,并能够设置最高和最低值。...

    linux下QT编程键盘键值捕获

    ### Linux 下 QT 编程键盘键值捕获详解 #### 一、背景介绍 在Linux环境下,使用QT进行GUI应用程序开发时,往往需要对用户的键盘输入做出响应。本文将详细介绍如何在QT应用程序中捕获并处理键盘键值,实现简单的键盘...

    inside QT(深入阅读QT源代码)

    - **在QT 2.x版本中**:在类定义时,只包含一个指向私有数据成员的指针。例如,在`Person`类中,我们只定义一个指向`PersonalDataPrivate`类型的指针,而具体的成员数据则存储在一个独立的`PersonalDataPrivate`对象...

    QT_Nokia内部培训资料

    - **内存管理**:探讨Qt中的内存管理机制,如智能指针的使用。 - **信号与槽**:深入理解Qt的信号与槽机制,这是Qt中实现事件驱动编程的关键。 - **QtGui控件**:详细介绍Qt提供的各种用户界面组件,如按钮、标签等...

    Qt实现的浮动工具箱

    **Qt实现的浮动工具箱详解** Qt是一个跨平台的应用程序开发框架,广泛应用于GUI编程,由C++编写。本项目是基于Qt实现的浮动工具箱,它允许用户在屏幕上自由移动和定位工具箱,增强了用户的交互体验。在这个系统中,...

    QT之TCP网络数据(文本和图像数据)传输

    QT是Qt Company开发的一种跨平台应用程序开发框架,广泛用于创建桌面、移动和嵌入式系统的用户界面和应用程序。TCP(Transmission Control Protocol)是一种面向连接的、可靠的、基于字节流的传输层通信协议,它是...

    QT教程及软件

    - **Qt学习之路文章列表**:一系列关于Qt学习的经验分享。 - **Qt的graphicsView框架**:深入探讨Qt中的图形视图框架。 - **AT2440EVB_II+WINCE5.0板上跑QT程序**:介绍如何在特定硬件平台上运行Qt程序。 - **OpenGL...

    基于QT的指南针显示

    【QT指南针显示详解】 QT,全称Qt,是一个跨平台的应用程序开发框架,由挪威的Trolltech公司(现为The Qt Company)开发,现在是Digia公司的子公司。它被广泛用于创建图形用户界面(GUI)应用程序,同时也支持...

    Qt最新官方API中文查考手册

    Qt是一个跨平台的应用程序开发框架,主要应用于图形用户界面(GUI)的开发,但同时也支持非GUI程序。它由The Qt Company提供,并且遵循LGPL和商业许可证。Qt API(应用程序编程接口)是Qt库的核心,包含了大量的类、...

    33Qt笔记一

    - **宏的作用**:`Q_OBJECT`宏是Qt的核心特性之一,用于声明类为QObject的子类,并允许该类使用信号与槽机制。 - **如何使用**:在类定义中包含`Q_OBJECT`宏。 **1.1.6 帮助和文档** - **官方文档**:Qt提供详尽的...

    QT5.1类图之间的抽象关系

    QT5.1是Qt库的一个重要版本,它包含了大量的C++类,这些类构成了一个强大的框架,用于构建图形用户界面(GUI)和其他跨平台应用程序。这张图表提供了对Qt5.1中类之间关系的直观理解,这对于深入学习和开发基于Qt的...

    QT高级编程(Advanced QT Programming)

    - **内存管理**:讨论Qt中的内存分配策略,如何有效地使用智能指针和资源管理类。 - **性能分析**:介绍如何使用Qt Creator中的性能分析工具来识别和解决性能瓶颈。 - **代码优化技巧**:提供一系列代码优化的...

Global site tag (gtag.js) - Google Analytics