`
bd2007
  • 浏览: 385610 次
  • 性别: 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-solutions-qt-solutions

    从Qt官网上下载的Qt的解决方案资料库(Qt Solutions Archive) http://qt.nokia.com/products/qt-addons/solutions-archive/ 有JPEG2000的,有Windows中Services的,有浏览器插件的,有单应用程序的,有SOAP的,有...

    QT Excel处理开源项目,github地址:https://github.com/QtExcel/QXlsx

    QT Excel处理开源项目,github地址:https://github.com/QtExcel/QXlsx

    udisks2-qt5-master_2qt5.com_www.2qt5·com_htts://www.2qt5.com_htt

    An application that work with udisk2 daemon

    Qt 教程代码 (http://c.biancheng.net/qt/)

    Qt教程 (http://c.biancheng.net/qt/)代码

    SDK文件下缺少emulator-2

    11:43 Emulator: [5984]:ERROR:android/android-emu/android/qt/qt_setup.cpp:28:Qt library not found at D:\AndroidSDK\emulator\lib64\qt\lib 11:43 Emulator: Could not launch 'D:\AndroidSDK\emulator/qemu/...

    QT-in-windows.zip_HTTP_QT5_Qt http下载文件_Qt5.2.1_qt http

    下载文件:qt-opensource-windows-x86-mingw48_opengl-5.2.1.exe并安装D:\Qt\Qt5.2.1 这个版本一般已经包含工具qt-creator和mingw32-make 若不含有,可以去下载IDE : ----打开网页:...

    QT+VLC实时流播放rtsp/rtmp

    QT+VLC实时流播放rtsp/rtmp 运行依赖QT5.9 VS2015 VLC-Qt是结合了Qt和libVLC的免费开源库。它包含了媒体播放的核心类以及一些GUI类,这些类加快了媒体播放器的开发。 VLC-Qt 封装了整个libVLC,因此具备libVLC的...

    Qt4.8.7及配套QtCreate5.0.3和MinGW4.8.2

    http://download.qt.io/official_releases/qt/4.8/4.8.7/ 下载文件:qt-opensource-windows-x86-mingw482-4.8.7.exe 二、mingw版本选择 从Qt官网的wiki找到 与Qt4.8.7配套的mingw的版本是mingw32_4.8.2 ...

    MainWindow.rar

    迷恋上QPainter画画,Qt圆弧仪表盘,大神请移步,开源,共享。 这个系统自定义积分,真的是烦。参考博文:https://blog.csdn.net/u013015629/article/details/95595757

    qt主题资源,dark风格

    在github上,我们搜索“Qt Creator theme”等关键字,很容易能找到一些比较好看的主题~。 这里,我先给出几个我在github等网站上收集到的几个挺好看的样式,需要的可以打开连接看一下: (1)IntellJ-Dracula: ...

    编译好的OpenCV3.4.2+Qt5.9+Contrib版

    编译教程请参考 ... 详细请参考档案中的ReadMe.txt OpenCV3.4.2 + Qt5.9.3 + CMake3.12.1 with Contrib3.4.2 ... CMake build tool: D:/Qt/Qt5.9.3/Tools/mingw530_32/bin/mingw32-make.exe Configuration: Release

    qt写扫雷程序

    qt编写的扫雷程序,实现步骤在CSDN链接:https://blog.csdn.net/qq_34359028/article/details/85226747

    编译好的OpenCV4.0.1+Qt5.9+Contrib版

    编译教程请参考 ... OpenCV4.0.1 + Qt5.9.3 + CMake3.12.1 with Contrib4.0.1 without hdf module ... CMake build tool: D:/Qt/Qt5.9.3/Tools/mingw530_32/bin/mingw32-make.exe Configuration: Release

    C++ QT SMTP 发送邮件(最新,成功的)

    //https://github.com/bluetiger9/SmtpClient-for-Qt/wiki //http://www.qtcentre.org/threads/44483-SMTP-Client-for-Qt /* git 命令: git clone https://github.com/bluetiger9/SmtpClient-for-Qt.git git ...

    【QT】翻金币项目(图片资源文件) res.zip

    【QT】翻金币项目(QT综合案例)中的对应图片资源文件:https://blog.csdn.net/hanhui22/article/details/109998453

    libstdc++.so.6.0.17(x64)

    Failed to load core: /opt/Qt5.2.1/Tools/QtCreator/lib/qtcreator/plugins/QtProject/libCore.so: Cannot load library /opt/Qt5.2.1/Tools/QtCreator/lib/qtcreator/plugins/QtProject/libCore.so: (/usr/lib64/...

    Qt QTreeView使用-QStandardItemModel的使用

    具体介绍见: Qt树形控件QTreeView使用1——节点的添加删除操作: http://blog.csdn.net/czyt1988/article/details/18996407 Qt树形控件QTreeView使用2——复选框的设置: ...利用C++11的function和bind功能,实现...

    QT-Plugin的创建和使用

    NULL 博文链接:https://cooker.iteye.com/blog/677339

Global site tag (gtag.js) - Google Analytics