对QT namespace UI的理解

首先得弄清plmpl的原理

转自http://blog.chinaunix.net/space.php?uid=21453418&do=blog&id=442302

相关的另外一骗好的文章

http://blog.csdn.net/pingf0/article/details/4571443

城门失火殃及池鱼
pImpl 惯用手法的运用方式大家都很清楚,其主要作用是解开类的使用接口和实现的耦合。如果不使用 pImpl 惯用手法,代码会像这样:
//c.hpp
#include<x.hpp>
class C
{
public:
void f1();
private:
X x; // X 的强耦合
};
像上面这样的代码, C 与它的实现就是强耦合的,从语义上说, x 成员数据是属于 C 的实现部分,不应该暴露给用户。从语言的本质上来说,在用户的代码中,每一次使用 ”new C” ”C c1” 这样的语句,都会将 X 的大小硬编码到编译后的二进制代码段中(如果 X 有虚函数,则还不止这些)——这是因为,对于 ”new C” 这样的语句,其实相当于 operator new(sizeof(C) ) 后面再跟上 C 的构造函数,而 ”C c1” 则是在当前栈上腾出 sizeof(C) 大小的空间,然后调用 C 的构造函数。因此,每次 X 类作了改动,使用 c.hpp 的源文件都必须重新编译一次,因为 X 的大小可能改变了。
在一个大型的项目中,这种耦合可能会对 build 时间产生相当大的影响。
pImpl 惯用手法可以将这种耦合消除,使用 pImpl 惯用手法的代码像这样:
//c.hpp
class X; // 用前导声明取代 include
class C
{
...
private:
X* pImpl; // 声明一个 X* 的时候, class X 不用完全定义
};
在一个既定平台上,任何指针的大小都是相同的。之所以分为 X* Y* 这些各种各样的指针,主要是提供一个高层的抽象语义,即该指针到底指向的是那个类的对象,并且,也给编译器一个指示,从而能够正确的对用户进行的操作(如调用 X 的成员函数)决议并检查。但是,如果从运行期的角度来说,每种指针都只不过是个 32 位的长整型(如果在 64 位机器上则是 64 位,根据当前硬件而定)。
正由于 pImpl 是个指针,所以这里 X 的二进制信息( sizeof(C) 等)不会被耦合到 C 的使用接口上去,也就是说,当用户 ”new C” ”C c1” 的时候,编译器生成的代码中不会掺杂 X 的任何信息,并且当用户使用 C 的时候,使用的是 C 的接口,也与 X 无关,从而 X 被这个指针彻底的与用户隔绝开来。只有 C 知道并能够操作 pImpl 成员指向的 X 对象。
防火墙
“修改 X 的定义会导致所有使用 C 的源文件重新编译”这种事就好比“城门失火,殃及池鱼”,其原因是“护城河”离“城门”太近了(耦合)。
pImpl 惯用手法又被成为“编译期防火墙”,什么是“防火墙”,指针?不是。 C++ 的编译模式为“分离式编译”,即不同的源文件是分开编译的。也就是说,不同的源文件之间有一道天然的防火墙,一个源文件“失火”并不会影响到另一个源文件。
但是,这里我们考虑的是头文件,如果头文件“失火”又当如何呢?头文件是不能直接编译的,它包含于源文件中,并作为源文件的一部分被一起编译。
这也就是说,如果源文件 S.cpp 使用了 C.hpp ,那么 class C 的(接口部分的)变动将无可避免的导致 S.CPP 的重新编译。但是作为 class C 的实现部分的 class X 却完全不应该导致 S.cpp 的重新编译。
因此,我们需要把 class X 隔绝在 C.hpp 之外。这样,每个使用 class C 的源文件都与 class X 隔离开来(与 class X 不在同一个编译单元)。但是,既然 class C 使用了 class X 的对象来作为它的实现部分,就无可避免的要“依赖”于 class X 。只不过,这个“依赖”应该被描述为:“ class C 的实现部分依赖于 class X ”,而不应该是“ class C 的用户使用接口部分依赖于 class X ”。
如果我们直接将 X 的对象写在 class C 的数据成员里面,则显而易见,使用 class C 的用户“看到”了不该“看到”的东西—— class X ——它们之间产生了耦合。然而,如果使用一个指向 class X 的指针,就可以将 X 的二进制信息“推”到 class C 的实现文件中去,在那里,我们 #include”x.hpp” ,定义所有的成员函数,并依赖于 X 的实现,这都无所谓,因为 C 的实现本来就依赖于 X ,重要的是:此时 class X 的改动只会导致 class C 的实现文件重新编译,而用户使用 class C 的源文件则安然无恙!
指针在这里充当了一座桥。将依赖信息“推”到了另一个编译单元,与用户隔绝开来。而防火墙是 C++ 编译器的固有属性。
穿越C++编译期防火墙
是什么穿越了 C++ 编译期防火墙?是指针!使用指针的源文件“知道”指针所指的是什么对象,但是不必直接“看到”那个对象——它可能在另一个编译单元,是指针穿越了编译期防火墙,连接到了那个对象。
从某种意义上说,只要是代表地址的符号都能够穿越 C++ 编译期防火墙,而代表结构 (constructs) 的符号则不能。
以上转自:
http://blog.csdn.net/armman/archive/2007/08/11/1737719.aspx
简单一句话,所谓的plmpl就是为了减少个个源文件之间的联系

接下来谈谈
namespace Ui
{
class Dialog: public Ui_Dialog {};
}

/********************************************/
dialog.h
/********************************************/
#ifndef DIALOG_H
#define DIALOG_H

#include <QtGui/QDialog>

namespace Ui
{
class Dialog;
}

class Dialog : public QDialog
{
Q_OBJECT

public:
Dialog(QWidget *parent = 0);
~Dialog();

private:
Ui::Dialog *ui;
};

#endif // DIALOG_H
/*********************************************/
ui_dialog.h
/*****************************************************************************/
/********************************************************************************
** Form generated from reading ui file 'dialog.ui'
**
** Created: Thu May 14 22:52:58 2009
** by: Qt User Interface Compiler version 4.5.0
**
** WARNING! All changes made in this file will be lost when recompiling ui file!
********************************************************************************/

#ifndef UI_DIALOG_H
#define UI_DIALOG_H

#include <QtCore/QVariant>
#include <QtGui/QAction>
#include <QtGui/QApplication>
#include <QtGui/QButtonGroup>
#include <QtGui/QDialog>
#include <QtGui/QHeaderView>
#include <QtGui/QListView>
#include <QtGui/QPushButton>

QT_BEGIN_NAMESPACE

class Ui_Dialog

public:
QListView *listView;
QPushButton *pushButton


void setupUi(QDialog *Dialog)
{
if (Dialog->objectName().isEmpty())
Dialog->setObjectName(QString::fromUtf8("Dialog"));
Dialog->resize(600, 400);
listView = new QListView(Dialog);
listView->setObjectName(QString::fromUtf8("listView"));
listView->setGeometry(QRect(30, 10, 256, 192));
pushButton = new QPushButton(Dialog);
pushButton->setObjectName(QString::fromUtf8("pushButton"));
pushButton->setGeometry(QRect(140, 280, 75, 23));

retranslateUi(Dialog);

QMetaObject::connectSlotsByName(Dialog);
} // setupUi

void retranslateUi(QDialog *Dialog)
{
Dialog->setWindowTitle(QApplication::translate("Dialog", "Dialog", 0, QApplication::UnicodeUTF8));
pushButton->setText(QApplication::translate("Dialog", "bye", 0, QApplication::UnicodeUTF8));
Q_UNUSED(Dialog);
} // retranslateUi

};

namespace Ui {
class Dialog: public Ui_Dialog {};
} // namespace Ui

QT_END_NAMESPACE

#endif // UI_DIALOG_H
/*******************************************************************************/

dialog.cpp
/*******************************************************************************/
#include "dialog.h"
#include "ui_dialog.h"

Dialog::Dialog(QWidget *parent)
: QDialog(parent), ui(new Ui::Dialog)
{
ui->setupUi(this);
//QObject::connect(ui->pushButton, SIGNAL(clicked()),this, SLOT(quit()));
}

Dialog::~Dialog()
{
delete ui;
}
/********************************************************************************/

ui_dialog.h

代码中有很多被硬编码的地方:
listView->setGeometry(QRect(30, 10, 256, 192));
pushButton = new QPushButton(Dialog);
pushButton->setObjectName(QString::fromUtf8("pushButton"));
pushButton->setGeometry(QRect(140, 280, 75, 23));

designer生成的这个东西, 如何让程序的其他代码去使用呢?

最直接的, 它应该产生一个:
class Ui_Dialog {
QListView *listView;
QPushButton *pushButton;
};

这样的类去让其他代码使用:

// My.h
#include "ui_dialog.h"
class My
{
Ui_Dialog dlg;
};


// My.cpp
#include "My.h"
// 实现My

但是这样存在问题, 如果ui_dialog.h文件的内容被改变, 不但My.cpp会被重新编译,
所有包含My.h的文件也都会被重新编译。
而且这确实是一个问题: designer确实经常被拖来拖去。


如果产生ui_dialog.h的那个程序能将如下代码:
listView->setGeometry(QRect(30, 10, 256, 192));
pushButton = new QPushButton(Dialog);
pushButton->setObjectName(QString::fromUtf8("pushButton"));
pushButton->setGeometry(QRect(140, 280, 75, 23));

移动到一个ui_dialog.cpp 中, 至少在移动dlg上的那些界面元素时, 只会重新编译ui_dialog.cpp。
不会修改ui_dialog.h, 也就不会引发另一连串重编译。

但是, 除了将界面元素拖来拖去, designer还经常干的一些事就是添加,删除一些界面元素,如:
class Ui_Dialog
{
public:
QListView *listView;
QPushButton *pushButton;
// ...
};

这样 ui_dialog.h 文件还是得改变。
如何让designer改变GUI外观后, 不会引发工程大范围的重新编译?

所以designer使用了pimpl手法 ……

前置声明一个 Ui:: Dialog类
namespace Ui {
class Dialog;
}

class Dialog : public QDialog {
Ui:: Dialog *ui; // 使用该类的一个指针
};

然后用户使用 dialog.h 头文件以及 Dialog类。
该文件被修改的频率就会低很多很多。
无论是将designer上的界面元素拖来拖去, 还是添加删除, dialog.h文件的内容——Dialog类的定义——都不会改变。

然后用户可以使用这个Dialog了:

#include "dialog.h"
class My
{
Dialog dlg;
};


在qt4中使用了继承的方式来使用designer创建的窗体,也就是同时继承QDialog和UI_Dialog。
而在Qt Creator自动创建的项目中,使用了组合的方式来使用Designer创建的窗体,就是集成QDialog,而将UI_Dialog作为一个成员变量来使用,也就是
private:
Ui::Dialog *ui;
在前一种方式中,你可以在继承类中直接使用UI_Dialog上的组件。在后一种方式中,你要使用ui->XXX的方式使用UI_Dialog上的组件。
两种方式都可以,但个人感觉第二种好一些,毕竟组合比集成的耦合度来的弱一些,就是稍有点麻烦,要加ui->,但同时也带来了更清晰的代码结构

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值