动态库 拷贝过去 L l
静态库能包进去
在Qt中,事件被封装成一个个对象,所有的事件均继承自抽象类QEvent. 接下来依次谈谈Qt中有谁来产生、分发、接受和处理事件:
1、谁来产生事件: 最容易想到的是我们的输入设备,比如键盘、鼠标产生的
keyPressEvent,keyReleaseEvent,mousePressEvent,mouseReleaseEvent事件(他们被封装成QMouseEvent和QKeyEvent),这些事件来自于底层的操作系统,它们以异步的形式通知Qt事件处理系统,后文会仔细道来。当然Qt自己也会产生很多事件,比如QObject::startTimer()会触发QTimerEvent. 用户的程序可还以自己定制事件。
2、谁来接受和处理事件:答案是QObject。在Qt的内省机制剖析一文已经介绍QObject 类是整个Qt对象模型的心脏,事件处理机制是QObject三大职责 (内存管理、内省(intropection)与事件处理制)之一。任何一个想要接受并处理事件的对象均须继承自QObject,可以选择重载QObject::event()函数或事件的处理权转给父类。
3、谁来负责分发事件:对于non-GUI的Qt程序,是由 QCoreApplication 负责将QEvent分发给QObject的子类-Receiver. 对于Qt GUI程序,由QApplication来负责。
接下来,将通过对代码的解析来看看QT是利用event loop从事件队列中获取用户输入事件,又是如何将事件转义成QEvents,并分发给相应的QObject处理。
- int main(int argc, char *argv[])
- {
- QApplication app(argc, argv);
- Widget window; // Widget 继承自QWidget
- window.show();
- return app.exec(); // 进入Qpplication事件循环
- }
以下是Qpplication事件循环的函数调用过程:
main(int, char **)
QApplication::exec()
QCoreApplication::exec()
QEventLoop::exec(ProcessEventsFlags ) //进入事件循环分发
QEventLoop::processEvents(ProcessEventsFlags ) //事件处理,该函数内区分调用平台相关的processEvents()函数
QEventDispatcherWin32::processEvents(QEventLoop::ProcessEventsFlags) //拿到用户的输入事件,调用windows API处理事件
以上总结为,Qt进入QApplication的event loop,经过层层委任,最
终QEventloop的processEvent将通过与平台相关的
QAbstractEventDispatcher的子类QEventDispatcherWin32获得用
户的用户输入事件,并将其打包成message后,通过标准Windows
API ,把消息传递给了Windows OS,Windows OS得到通知后回调
QtWndProc, 至此事件的分发与处理完成了一半的路程;
+++++++++++++++++++++++++++++++++++++++++++++++
++++++++++++++++++++++++++++++++++++++++++++++++
事件经OS处理完毕,回调到widget过程:
QT_WIN_CALLBACK QtWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) //拿到Widget指针和事件,用于回调
bool QETWidget::translateMouseEvent(const MSG &msg) //该函数所在与Windows平台相关,主要职责就是把已windows格式打包的鼠标事件解包、翻译成QApplication可识别的QMouseEvent,QWidget,call QApplicationPrivate::sendMouseEvent(...)
//至此与平台相关代码处理完毕
//MouseEvent默认的发送方式是spontaneous, 所以将执行sendSpontaneousEvent。 sendSpontaneousEvent() 与 sendEvent的代码实现几乎相同,
除了将QEvent的属性spontaneous标记不同。 这里是解释什么spontaneous事件:如果事件由应用程序之外产生的,比如一个系统事件。
显然MousePress事件是由视窗系统产生的一个的事件,因此它是 spontaneous事件
bool QApplicationPrivate::sendMouseEvent(...) // 判断出事件类型来决定调用QApplication::sendSpontaneousEvent()或是QApplication::sendEvent()
inline bool QCoreApplication::sendSpontaneousEvent(QObject *receiver, QEvent *event) //调用 notifyInternal()
bool QCoreApplication::notifyInternal(QObject *receiver, QEvent *event) //call notify()
// QCoreApplication::notify和它的重载函数QApplication::notify在Qt的派发过程中起到核心的作用,Qt的官方文档时这样说的:
任何线程的任何对象的所有事件在发送时都会调用notify函数.
bool QApplication::notify(QObject *receiver, QEvent *e) //里面主要是一个switch case结构来处理不同的事件,call notify_helper()进一步处理具体事件
bool QApplicationPrivate::notify_helper(QObject *receiver, QEvent * e) //主要先将事件交给事件过滤器来处理(如果安装有时间过滤器的话),最后call event()
bool QWidget::event(QEvent *event) //通过switch case 将事件交给具体的事件handler,如mousePressEvent()完成整个事件循环过程。
事件过滤器机制
Qt提供5个级别的事件处理和过滤:
1,重新实现事件函数。 比如: mousePressEvent(), keyPress-Event(), paintEvent() 。
这是最常规的事件处理方法。
2,重新实现QObject::event().
这一般用在Qt没有提供该事件的处理函数时。也就是,我们增加新的事件时。
3,安装事件过滤器
4,在 QApplication 上安装事件过滤器。
这之所以被单独列出来是因为: QApplication 上的事件过滤器将捕获应用程序的所有事件,而且第一个获得该事件。也就是说事件在发送给其它任何一个event filter之前发送给QApplication的event filter。
5,重新实现QApplication 的 notify()方法.
Qt使用 notify()来分发事件。要想在任何事件处理器捕获事件之前捕获事件,唯一的方法就是重新实现QApplication 的 notify()方法。
/****************************************************************************/
Qt创建了QEvent事件对象之后,会调用QObject的event()函数做事件的分发。有时候,你可能需要在调用event()函数之前做一些另外的操作,比如,对话框上某些组件可能并不需要响应回车按下的事件,此时,你就需要重新定义组件的event()函数。如果组件很多,就需要重写很多次event()函数,这显然没有效率。为此,你可以使用一个事件过滤器,来判断是否需要调用event()函数。
QOjbect有一个eventFilter()函数,用于建立事件过滤器。这个函数的签名如下:
virtual bool QObject::eventFilter ( QObject * watched, QEvent * event )
如果watched对象安装了事件过滤器,这个函数会被调用并进行事件过滤,然后才轮到组件进行事件处理。在重写这个函数时,如果你需要过滤掉某个事件,例如停止对这个事件的响应,需要返回true。
bool MainWindow::eventFilter(QObject *obj, QEvent *event)
{
if (obj == textEdit) {
if (event->type() == QEvent::KeyPress) {
QKeyEvent *keyEvent = static_cast(event);
qDebug() << "Ate key press" << keyEvent->key();
return true;
} else {
return false;
}
} else {
// pass the event on to the parent class
return QMainWindow::eventFilter(obj, event);
}
}
上面的例子中为MainWindow建立了一个事件过滤器。为了过滤某个组件上的事件,首先需要判断这个对象是哪个组件,然后判断这个事件的类型。例如,我不想让textEdit组件处理键盘事件,于是就首先找到这个组件,如果这个事件是键盘事件,则直接返回true,也就是过滤掉了这个事件,其他事件还是要继续处理,所以返回false。对于其他组件,我们并不保证是不是还有过滤器,于是最保险的办法是调用父类的函数。
在创建了过滤器之后,下面要做的是安装这个过滤器。安装过滤器需要调用installEventFilter()函数。这个函数的签名如下:
void QObject::installEventFilter ( QObject * filterObj )
这个函数是QObject的一个函数,因此可以安装到任何QObject的子类,并不仅仅是UI组件。这个函数接收一个QObject对象,调用了这个函数安装事件过滤器的组件会调用filterObj定义的eventFilter()函数。例如,textField.installEventFilter(obj),则如果有事件发送到textField组件是,会先调用obj->eventFilter()函数,然后才会调用textField.event()。
当然,你也可以把事件过滤器安装到QApplication上面,这样就可以过滤所有的事件,已获得更大的控制权。不过,这样做的后果就是会降低事件分发的效率。
如果一个组件安装了多个过滤器,则最后一个安装的会最先调用,类似于堆栈的行为。
注意,如果你在事件过滤器中delete了某个接收组件,务必将返回值设为true。否则,Qt还是会将事件分发给这个接收组件,从而导致程序崩溃。
事件过滤器和被安装的组件必须在同一线程,否则,过滤器不起作用。另外,如果在install之后,这两个组件到了不同的线程,那么,只有等到二者重新回到同一线程的时候过滤器才会有效。
事件的调用最终都会调用QCoreApplication的notify()函数,因此,最大的控制权实际上是重写QCoreApplication的notify()函数。由此可以看出,Qt的事件处理实际上是分层五个层次:重定义事件处理函数,重定义event()函数,为单个组件安装事件过滤器,为QApplication安装事件过滤器,重定义QCoreApplication的notify()函数。这几个层次的控制权是逐层增大的。
以上出处:http://mobile.51cto.com/symbian-272812.htm
http://download.csdn.net/detail/foolish1212/5330380
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Qt Event Dispatcher学习
距离上次整理Qt事件系统已经过了14个月了。重新理理思绪:
Qt中的事件大致可分为3类:
Spontaneous events | 从系统得到的消息:鼠标按键、键盘按键、定时器事件等。转化为QEvent后被Qt事件系统依次处理 |
Posted events | 由Qt或应用程序直接生成,放入Qt消息队列 QCoreApplication::postEvent() |
Sent events | 由Qt或应用程序产生,不放入队列直接被派发和处理 QCoreApplication::sendEvent() |
自发的; 自然的; 天然产生的; 无意识的------------------------Spontaneous |
本文中,先简单看一下后两种,然后重点看看第一种。
Sent events
比如,发送按键"X"的事件到 mainWin 窗口。
QKeyEvent event(QEvent::KeyPress, Qt::Key_X, Qt::NoModifier, "X", 0
);
QApplication::sendEvent(mainWin, &event);
如果没有过滤器的话,这其实就是直接调用 mainWin 的 keyPressEvent()函数。
(图片来源:http://www.slideshare.net/mariusbu/qt-widgets-in-depth,下同)
这个东西不涉及事件队列、事件循环等等东西。但是事件过滤在这个过程中正常起作用。而且除过滤器外,下面三个函数在派生类中都可以被覆盖(以处理这个事件):
- QApplication::notify()
- QWidget::event()
- QWidget::keyPressEvent()
Posted events
比如,同样是发送按键"X"的事件到 mainWin 窗口,我们可以使用postEvent()。
QApplication::postEvent(mainWin, new QKeyEvent(QEvent::KeyPress, Qt::Key_X, Qt::NoModifier, "X", 0
));
这会将该事件放入Qt自己的事件队列中,事件循环QEventLoop空闲时会判断该队列是否为空。最终使用 sendEvent() 依次派发事件队列中的这些事件。
也可以手动使用
- QCoreApplication::sendPostedEvents()
清空当前线程事件队列(即派发队列中的事件)
注意:每一个线程有一个事件队列。
Spontaneous events
系统底层事件是通过 QAbstractEventDispatcher 整合进Qt的事件循环的。
Event dispatcher接受窗口系统以及其他源中的事件。它对事件的传递提供了一种精细控制的能力。
QAbstractEventDispatcher
- QEventDispatcherUNIX
- QEventDispatcherX11
- QEventDispatcherQWS
- QEventDispatcherQPA
- QEventDispatcherGlib
- QGuiEventDispatcherGlib
- QWSEventDispatcherGlib
- QEventDispatcherWin32
- QEventDispatcherMac
- ...
这堆东西还挺多,不过下面三个属于QtCore模块
QEventDispatcherGlib | 使用glib事件循环,有助于和Gtk的集成 |
QEventDispatcherUNIX | 默认的glib不可用时,就用这个喽 |
QEventDispatcherWin32 | Qt 创建一个带回调函数的隐藏窗口来处理事件。 |
我们能看的到的就是,它们提供
的注册、反注册功能。并将系统底层对应事件转换成Qt事件。
其他的属于QtGui模块。就是和窗口系统(重绘、移动等等事件)以及键鼠事件有关了。
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
转载自:http://mobile.51cto.com/symbian-272812.htm,在此谢谢原作者的分享!
------------------------第一部分----------------------
本篇来介绍Qt 事件处理机制 。深入了解事件处理系统对于每个学习Qt人来说非常重要,可以说,Qt是以事件驱动的UI工具集。 大家熟知Signals/Slots在多线程的实现也依赖于Qt的事件处理机制。
在Qt中,事件被封装成一个个对象,所有的事件均继承自抽象类QEvent. 接下来依次谈谈Qt中有谁来产生、分发、接受和处理事件:
1、谁来产生事件: 最容易想到的是我们的输入设备,比如键盘、鼠标产生的
keyPressEvent,keyReleaseEvent,mousePressEvent,mouseReleaseEvent事件(他们被封装成QMouseEvent和QKeyEvent),这些事件来自于底层的操作系统,它们以异步的形式通知Qt事件处理系统,后文会仔细道来。当然Qt自己也会产生很多事件,比如QObject::startTimer()会触发QTimerEvent. 用户的程序可还以自己定制事件。
2、谁来接受和处理事件:答案是QObject。在Qt的内省机制剖析一文已经介绍QObject 类是整个Qt对象模型的心脏,事件处理机制是QObject三大职责(内存管理、内省(intropection)与事件处理制)之一。任何一个想要接受并处理事件的对象均须继承自QObject,可以选择重载QObject::event()函数或事件的处理权转给父类。
3、谁来负责分发事件:对于non-GUI的Qt程序,是由QCoreApplication负责将QEvent分发给QObject的子类-Receiver. 对于Qt GUI程序,由QApplication来负责。
接下来,将通过对代码的解析来看看QT是利用event loop从事件队列中获取用户输入事件,又是如何将事件转义成QEvents,并分发给相应的QObject处理。
#include <QApplication>
#include "widget.h"
//Section 1
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
Widget window; // Widget 继承自QWidget
window.show();
return app.exec(); // 进入Qpplication事件循环,见section 2
}
// Section 2:
int QApplication::exec()
{
//skip codes
//简单的交给QCoreApplication来处理事件循环=〉section 3
return QCoreApplication::exec();
}
// Section 3
int QCoreApplication::exec()
{
//得到当前Thread数据
QThreadData *threadData = self->d_func()->threadData;
if (threadData != QThreadData::current()) {
qWarning("%s::exec: Must be called from the main thread", self->metaObject()->className());
return -1;
}
//检查event loop是否已经创建
if (!threadData->eventLoops.isEmpty()) {
qWarning("QCoreApplication::exec: The event loop is already running");
return -1;
}
...
QEventLoop eventLoop;
self->d_func()->in_exec = true;
self->d_func()->aboutToQuitEmitted = false;
//委任QEventLoop 处理事件队列循环 ==> Section 4
int returnCode = eventLoop.exec();
....
}
return returnCode;
}
// Section 4
int QEventLoop::exec(ProcessEventsFlags flags)
{
//这里的实现代码不少,最为重要的是以下几行
Q_D(QEventLoop); // 访问QEventloop私有类实例d
try {
//只要没有遇见exit,循环派发事件
while (!d->exit)
processEvents(flags | WaitForMoreEvents | EventLoopExec);
} catch (...) {}
}
// Section 5
bool QEventLoop::processEvents(ProcessEventsFlags flags)
{
Q_D(QEventLoop);
if (!d->threadData->eventDispatcher)
return false;
if (flags & DeferredDeletion)
QCoreApplication::sendPostedEvents(0, QEvent::DeferredDelete);
//将事件派发给与平台相关的QAbstractEventDispatcher子类 =>Section 6
return d->threadData->eventDispatcher->processEvents(flags);
}
// Section 6,QTDIR\src\corelib\kernel\qeventdispatcher_win.cpp
internal内部的
// 这段代码是完成与windows平台相关的windows c++。 以跨平台著称的Qt同时也提供了对Symiban,Unix等平台的消息派发支持
// 其事现分别封装在QEventDispatcherSymbian和QEventDispatcherUNIX
// QEventDispatcherWin32派生自QAbstractEventDispatcher.
bool QEventDispatcherWin32::processEvents(QEventLoop::ProcessEventsFlags flags)
{
Q_D(QEventDispatcherWin32);
if (!d->internalHwnd)
createInternalHwnd();
d->interrupt = false;
emit awake();
bool canWait;
bool retVal = false;
bool seenWM_QT_SENDPOSTEDEVENTS = false;
bool needWM_QT_SENDPOSTEDEVENTS = false;
do {
DWORD waitRet = 0;
HANDLE pHandles[MAXIMUM_WAIT_OBJECTS - 1];
QVarLengthArray<MSG> processedTimers;
while (!d->interrupt) {
DWORD nCount = d->winEventNotifierList.count();
Q_ASSERT(nCount < MAXIMUM_WAIT_OBJECTS - 1);
MSG msg;
bool haveMessage;
if (!(flags & QEventLoop::ExcludeUserInputEvents) && !d->queuedUserInputEvents.isEmpty()) {
// process queued user input events
haveMessage = true;
//从处理用户输入队列中取出一条事件
msg = d->queuedUserInputEvents.takeFirst();
} else if(!(flags & QEventLoop::ExcludeSocketNotifiers) && !d->queuedSocketEvents.isEmpty()) {
// 从处理socket队列中取出一条事件
haveMessage = true;
msg = d->queuedSocketEvents.takeFirst();
} else {
haveMessage = PeekMessage(&msg, 0, 0, 0, PM_REMOVE);
if (haveMessage && (flags & QEventLoop::ExcludeUserInputEvents)
&& ((msg.message >= WM_KEYFIRST
&& msg.message <= WM_KEYLAST)
|| (msg.message >= WM_MOUSEFIRST
&& msg.message <= WM_MOUSELAST)
|| msg.message == WM_MOUSEWHEEL
|| msg.message == WM_MOUSEHWHEEL
|| msg.message == WM_TOUCH
#ifndef QT_NO_GESTURES
|| msg.message == WM_GESTURE
|| msg.message == WM_GESTURENOTIFY
#endif
|| msg.message == WM_CLOSE)) {
// 用户输入事件入队列,待以后处理
haveMessage = false;
d->queuedUserInputEvents.append(msg);
}
if (haveMessage && (flags & QEventLoop::ExcludeSocketNotifiers)
&& (msg.message == WM_QT_SOCKETNOTIFIER && msg.hwnd == d->internalHwnd)) {
// socket 事件入队列,待以后处理
haveMessage = false;
d->queuedSocketEvents.append(msg);
}
}
....
if (!filterEvent(&msg)) {
TranslateMessage(&msg);
//将事件打包成message调用Windows API派发出去
//分发一个消息给窗口程序。消息被分发到回调函数,将消息传递给windows系统,windows处理完毕,会调用回调函数 =>
section 7
DispatchMessage(&msg);
}
}
}
} while (canWait);
...
return retVal;
}
// Section 7 windows窗口回调函数 定义在QTDIR\src\gui\kernel\qapplication_win.cpp
extern "C" LRESULT QT_WIN_CALLBACK QtWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
...
//将消息重新封装成QEvent的子类QMouseEvent ==> Section 8
result = widget->translateMouseEvent(msg);
...
}
从Section 1~Section7, Qt进入QApplication的event loop,经过层层委任,最终QEventloop的processEvent将通过与平台相关的QAbstractEventDispatcher的子类QEventDispatcherWin32获得用户的用户输入事件,并将其打包成message后,通过标准Windows API ,把消息传递给了Windows OS,Windows OS得到通知后回调QtWndProc, 至此事件的分发与处理完成了一半的路程。
小结:Qt 事件处理机制 (上篇)的内容介绍完了,在下文中,我们将进一步讨论当我们收到来在Windows的回调后,事件又是怎么一步步打包成QEvent并通过QApplication分发给最终事件的接受和处理者QObject::event.请继续看Qt 事件处理机制 (下篇)。最后希望本文能帮你解决问题!
---------------------------第二部分----------------------------
继续我们上一篇文章继续介绍,Qt 事件处理机制 (上篇) 介绍了Qt框架的事件处理机制:事件的产生、分发、接受和处理,并以视窗系统鼠标点击QWidget为例,对代码进行了剖析,向大家分析了Qt框架如何通过Event Loop处理进入处理消息队列循环,如何一步一步委派给平台相关的函数获取、打包用户输入事件交给视窗系统处理,函数调用栈如下:
main(int, char **)
QApplication::exec()
QCoreApplication::exec()
QEventLoop::exec(ProcessEventsFlags )
QEventLoop::processEvents(ProcessEventsFlags )
QEventDispatcherWin32::processEvents(QEventLoop::ProcessEventsFlags)
本文将介绍Qt app在视窗系统回调后,事件又是怎么一步步通过QApplication分发给最终事件的接受和处理者QWidget::event, (QWidget继承Object,重载其虚函数event),以下所有的讨论都将嵌入在源码之中。
QT_WIN_CALLBACK QtWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) bool QETWidget::translateMouseEvent(const MSG &msg)
bool QApplicationPrivate::sendMouseEvent(...)
inline bool QCoreApplication::sendSpontaneousEvent(QObject *receiver, QEvent *event)
bool QCoreApplication::notifyInternal(QObject *receiver, QEvent *event)
bool QApplication::notify(QObject *receiver, QEvent *e)
bool QApplicationPrivate::notify_helper(QObject *receiver, QEvent * e)
bool QWidget::event(QEvent *event)
// (续上文Section 7) Section 2-1:
QT_WIN_CALLBACK QtWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
...
//检查message是否属于Qt可转义的鼠标事件
if (qt_is_translatable_mouse_event(message)) {
if (QApplication::activePopupWidget() != 0) {
POINT curPos = msg.pt;
//取得鼠标点击坐标所在的QWidget指针,它指向我们在main创建的widget实例
QWidget* w = QApplication::widgetAt(curPos.x, curPos.y);
if (w)
widget = (QETWidget*)w;
}
if (!qt_tabletChokeMouse) {
//对,就在这里。Windows的回调函数将鼠标事件分发回给了Qt Widget
// => Section 2-2
result = widget->translateMouseEvent(msg);
...
}
// Section 2-2 $QTDIR\src\gui\kernel\qapplication_win.cpp
//该函数所在与Windows平台相关,主要职责就是把已windows格式打包的鼠标事件解包、翻译成QApplication可识别的QMouseEvent,QWidget.
bool QETWidget::translateMouseEvent(const MSG &msg)
{
//.. 这里很长的代码给以忽略
// 让我们看一下sendMouseEvent的声明
// widget是事件的接受者; e是封装好的QMouseEvent
// ==> Section 2-3
res = QApplicationPrivate::sendMouseEvent(widget, &e, alienWidget, this, &qt_button_down, qt_last_mouse_receiver);
}
// Section 2-3 $QTDIR\src\gui\kernel\qapplication.cpp
bool QApplicationPrivate::sendMouseEvent(QWidget *receiver, QMouseEvent *event,
QWidget *alienWidget, QWidget *nativeWidget,
QWidget **buttonDown, QPointer<QWidget> &lastMouseReceiver,
bool spontaneous)
{
//至此与平台相关代码处理完毕
//MouseEvent默认的发送方式是spontaneous, 所以将执行sendSpontaneousEvent。 sendSpontaneousEvent() 与 sendEvent的代码实现几乎相同,
除了将QEvent的属性spontaneous标记不同。 这里是解释什么spontaneous事件:如果事件由应用程序之外产生的,比如一个系统事件。
显然MousePress事件是由视窗系统产生的一个的事件(详见上文Section 1~ Section 7),因此它是 spontaneous事件
if (spontaneous)
result = QApplication::sendSpontaneousEvent(receiver, event); ==〉Section 2-4
else
result = QApplication::sendEvent(receiver, event);
}
// Section 2-4 C:\Qt\4.7.1-Vs\src\corelib\kernel\qcoreapplication.h
inline bool QCoreApplication::sendSpontaneousEvent(QObject *receiver, QEvent *event)
{
//将event标记为自发事件
//进一步调用 2-5 QCoreApplication::notifyInternal
if (event) event->spont = true; return self ? self->notifyInternal(receiver, event) : false;
}
// Section 2-5: $QTDIR\gui\kernel\qapplication.cpp
bool QCoreApplication::notifyInternal(QObject *receiver, QEvent *event)
{
// 几行代码对于Qt Jambi (QT Java绑定版本) 和QSA (QT Script for Application)的支持
...
// 以下代码主要意图为Qt强制事件只能够发送给当前线程里的对象,也就是说receiver->d_func()->threadData应该等于QThreadData::current()。
注意,跨线程的事件需要借助Event Loop来派发
QObjectPrivate *d = receiver->d_func();
QThreadData *threadData = d->threadData;
++threadData->loopLevel;
bool returnValue;
QT_TRY {
//哇,终于来到大名鼎鼎的函数QCoreApplication::nofity()了 ==> Section 2-6
returnValue = notify(receiver, event);
} QT_CATCH (...) {
--threadData->loopLevel;
QT_RETHROW;
}
}
// Section 2-6: $QTDIR\gui\kernel\qapplication.cpp
// QCoreApplication::notify和它的重载函数QApplication::notify在Qt的派发过程中起到核心的作用,Qt的官方文档时这样说的:
任何线程的任何对象的所有事件在发送时都会调用notify函数。
bool QApplication::notify(QObject *receiver, QEvent *e)
{
//代码很长,最主要的是一个大大的Switch,Case
..
switch ( e->type())
{
...
case QEvent::MouseButtonPress:
case QEvent::MouseButtonRelease:
case QEvent::MouseButtonDblClick:
case QEvent::MouseMove:
...
//让自己私有类(d是私有类的句柄)来进一步处理 ==> Section 2-7
res = d->notify_helper(w, w == receiver ? mouse : &me);
e->spont = false;
break;
}
...
}
// Section 2-7: $QTDIR\gui\kernel\qapplication.cpp
bool QApplicationPrivate::notify_helper(QObject *receiver, QEvent * e)
{
...
// 向事件过滤器发送该事件,这里介绍一下Event Filters. 事件过滤器是一个接受即将发送给目标对象所有事件的对象。
//如代码所示它开始处理事件在目标对象行动之前。过滤器的QObject::eventFilter()实现被调用,能接受或者丢弃过滤,
允许或者拒绝事件的更进一步的处理。如果所有的事件过滤器允许更进一步的事件处理,事件将被发送到目标对象本身。
如果他们中的一个停止处理,目标和任何后来的事件过滤器不能看到任何事件。
if (sendThroughObjectEventFilters(receiver, e))
return true;
// 递交事件给receiver => Section 2-8
bool consumed = receiver->event(e);
e->spont = false;
}
// Section 2-8 $QTDIR\gui\kernel\qwidget.cpp
// QApplication通过notify及其私有类notify_helper,将事件最终派发给了QObject的子类- QWidget.
bool QWidget::event(QEvent *event)
{
...
switch(event->type()) {
case QEvent::MouseButtonPress:
// Don't reset input context here. Whether reset or not is
// a responsibility of input method. reset() will be
// called by mouseHandler() of input method if necessary
// via mousePressEvent() of text widgets.
#if 0
resetInputContext();
#endif
//mousePressEvent是虚函数,QWidget的子类可以通过重载重新定义mousePress事件的行为
mousePressEvent((QMouseEvent*)event);
break;
}
小结:Qt 事件处理机制 (下篇)的内容介绍完了,希望本文对你 有所帮助!更多相关资料请参考编辑推荐!
注:转载时删除了原文中一些重复的地方。
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
级别 | 函数 | 文件 | 行号 |
0 | qt_reg_winclass | qapplication_win.cpp | 966 |
1 | QWidgetPrivate::create_sys | qwidget_win.cpp | 283 |
2 | QWidget::create | qwidget.cpp | 1511 |
3 | QWidget::setVisible | qwidget.cpp | 7708 |
4 | QWidget::show | qwidget.h | 494 |
5 | qMain | main.cpp | 8 |
6 | WinMain | qtmain_win.cpp | 131 |
7 | main | | 0 |
级别 | 函数 | 文件 | 行号 |
0 | QEventDispatcherWin32::processEvents | qeventdispatcher_win.cpp | 712 |
1 | QGuiEventDispatcherWin32::processEvents | qapplication_win.cpp | 1202 |
2 | QEventLoop::processEvents | qeventloop.cpp | 149 |
3 | QEventLoop::exec | qeventloop.cpp | 204 |
4 | QCoreApplication::exec | qcoreapplication.cpp | 1187 |
5 | QApplication::exec | qapplication.cpp | 3812 |
6 | qMain | main.cpp | 10 |
7 | WinMain | qtmain_win.cpp | 131 |
8 | main | | 0 |
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
我们知道windows窗口程序的程序入口是WinMain函数,一般在这个函数中做三件事情:注册窗口类别、建立窗口并在屏幕上显示、进入事件循环,不断从事件队列中取出消息来处理。
同样在QT中windows编程中的入口函数也是WinMain,而不是main.cpp中的int main(int argc, char *argv[])函数。
在分析QT源码后发现,在qtmain_win.cpp文件中有这个函数。
int APIENTRY WinMain(HINSTANCE instance, HINSTANCE prevInstance, LPSTR /*cmdParamarg*/, int cmdShow)
{
QByteArray cmdParam = QString::fromWCharArray(GetCommandLine()).toLocal8Bit();
int argc = 0;
QVector<char *> argv(8);
qWinMain(instance, prevInstance, cmdParam.data(), cmdShow, argc, argv);
int result = main(argc, argv.data());
return result;
}
可以看到WinMain函数先调用了qWinMain函数,然后是main.cpp中的main函数。我们首先来看看qWinMain函数做了一些什么。
void qWinMain(HINSTANCE instance, HINSTANCE prevInstance, LPSTR cmdParam,
int cmdShow, int &argc, QVector<char *> &argv)
{
static bool already_called = false;
if (already_called) {
qWarning("Qt: Internal error: qWinMain should be called only once");
return;
}
already_called = true;
usingWinMain = true;
// Install default debug handler
qInstallMsgHandler(qWinMsgHandler);
// Create command line
argv = qWinCmdLine<char>(cmdParam, int(strlen(cmdParam)), argc);
appCmdShow = cmdShow;
}
它,它...其实啥都没做。
我们再看main函数
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
}
在这里我们可以看到首先创建了一个QApplication类的实例。QApplication类继承自QCoreApplication类,在QCoreApplication的构造函数中会调用其初始化函数init()。
void QCoreApplication::init()
{
// ...其他代码
//otherwise we create one
if (!QCoreApplicationPrivate::eventDispatcher)
d->createEventDispatcher();
Q_ASSERT(QCoreApplicationPrivate::eventDispatcher != 0);
if (!QCoreApplicationPrivate::eventDispatcher->parent())
QCoreApplicationPrivate::eventDispatcher->moveToThread(d->threadData->thread);
d->threadData->eventDispatcher = QCoreApplicationPrivate::eventDispatcher;
d->processCommandLineArguments();
qt_startup_hook();
}
在这里会调用createEventDisPatcher这个函数。
注意这个函数是void QApplicationPrivate::createEventDispatcher()
而不是void QCoreApplicationPrivate::createEventDispatcher()。
在函数createEventDispatcher()中会调用QEventDispatcherWin32::createInternalHwnd()。
void QEventDispatcherWin32::createInternalHwnd()
{
Q_D(QEventDispatcherWin32);
Q_ASSERT(!d->internalHwnd);
if (d->internalHwnd)
return;
d->internalHwnd = qt_create_internal_window(this);
// setup GetMessage hook needed to drive our posted events
d->getMessageHook = SetWindowsHookEx(WH_GETMESSAGE, (HOOKPROC) qt_GetMessageHook, NULL, GetCurrentThreadId());
// trigger a call to sendPostedEvents()
wakeUp();
}
这个函数将会调用函数 qt_create_internal_window创建一个内部窗口。
static HWND qt_create_internal_window(const QEventDispatcherWin32 *eventDispatcher)
{
// make sure that multiple Qt's can coexist in the same process
QString className = QLatin1String("QEventDispatcherWin32_Internal_Widget") + QString::number(quintptr(qt_internal_proc));
WNDCLASS wc;
wc.style = 0;
wc.lpfnWndProc = qt_internal_proc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = qWinAppInst();
wc.hIcon = 0;
wc.hCursor = 0;
wc.hbrBackground = 0;
wc.lpszMenuName = NULL;
wc.lpszClassName = reinterpret_cast<const wchar_t *> (className.utf16());
RegisterClass(&wc);
HWND wnd = CreateWindow(wc.lpszClassName, // classname
wc.lpszClassName, // window name
0, // style
0, 0, 0, 0, // geometry
0, // parent
0, // menu handle
qWinAppInst(), // application
0); // windows creation data.
if (!wnd) {
qWarning("QEventDispatcher: Failed to create QEventDispatcherWin32 internal window: %d\n", (int)GetLastError());
}
SetWindowLongPtr(wnd, GWLP_USERDATA, (LONG_PTR)eventDispatcher);
return wnd;
}
在LRESULT QT_WIN_CALLBACK qt_internal_proc(HWND hwnd, UINT message, WPARAM wp, LPARAM lp)函数中,将调用QCoreApplicationPrivate::sendPostedEvents(0, 0, d->threadData)来发送消息队列中的消息。这个函数就是QT事件派发的关键。
那么鼠标键盘等窗口消息在哪里处理呢?程序窗口又在哪里创建呢?
不再像上面那么啰嗦,直接给出调用堆栈。
111111111111111111111111111111111111111111111111111111111111111111111111
111111111111111111111111111111111111111111111111111111111111111111
我们可以看到在调用堆栈中有show函数,这正是w.show()。在栈顶是qt_reg_winclass函数,正是在这个函数中程序窗口被创建,下面给出这个函数。
const QString qt_reg_winclass(QWidget *w) // register window class
{
//。。。。。。其他省略代码
WNDCLASSEX wc;
wc.cbSize = sizeof(WNDCLASSEX);
wc.style = style;
wc.lpfnWndProc = (WNDPROC)QtWndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = qWinAppInst();
wc.hIcon = (HICON)LoadImage(qWinAppInst(), L"IDI_ICON1", IMAGE_ICON, 0, 0, LR_DEFAULTSIZE);
wc.hCursor = 0;
HBRUSH brush = 0;
if (w && !qt_widget_private(w)->isGLWidget)
brush = (HBRUSH)GetSysColorBrush(COLOR_WINDOW);
wc.hbrBackground = brush;
wc.lpszMenuName = 0;
wc.lpszClassName = (wchar_t*)cname.utf16();
ATOM atom = RegisterClassEx(&wc);
}
在这里我们需要关心的是QtWndProc函数。正是在这个函数把WM消息转化成QT内部事件,发送给widget。当然消息转换之前会调用widget的winEvent函数,这个函数可以自己处理一些WM消息,并可以过滤掉。
最后我们需要找到消息派发的那个大循环。还是根据调用堆栈给出调用关系。
2222222222222222222222222222222222222222222222222222222222222222222222222222
2222222222222222222222222222222222222222222222222222222222222
我们看到大循环由QApplication::exec()函数调用,大循环的主要代码在函数QEventDispatcherWin32::processEvents中,下面给出这个函数。
bool QEventDispatcherWin32::processEvents(QEventLoop::ProcessEventsFlags flags)
{
//.....................其他代码
while (!d->interrupt) {
//.....................其他代码
haveMessage = PeekMessage(&msg, 0, 0, 0, PM_REMOVE);
//.....................其他代码
if (!filterEvent(&msg)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
//.....................其他代码
}
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
解读 Q_D, Q_Q 指针
见 qglog.h文件定义:
#define Q_D(Class) Class##Private * const d = d_func()
#define Q_Q(Class) Class * const q = q_func()
d指针是在主类中使用的,来获取私有子类成员指针
q指针是在私有数据类中使用的,来获取主类对象指针
D-指针
私有成员总是不可见的,Qt中私有成员不仅仅是简单封装一下,将访问权限改为private,它将所有私有数据封装在私有类里(命名就是 classname##private), 这样一来连用户都不知道他到底封装了什么,程序中只有这个私有类成员指针,这个指针就是D-指针。
从QObject开始看
- class QObject
- {
- Q_DECLARE_PRIVATE(QObject)
- public:
- Q_INVOKABLE explicit QObject(QObject *parent=0);
- protected:
- QObject(QObjectPrivate &dd, QObject *parent = 0);
- QScopedPointer<QObjectData> d_ptr;
-
- }
展开后
- class QObject
- {
- inline QObjectPrivate* d_func() { return reinterpret_cast(qGetPtrHelper(d_ptr)); }
- inline const QObjectPrivate* d_func() const { return reinterpret_cast(qGetPtrHelper(d_ptr)); }
- friend class QObjectPrivate;
- public:
- Q_INVOKABLE explicit QObject(QObject *parent=0);
- protected:
- QObject(QObjectPrivate &dd, QObject *parent = 0);
- QScopedPointer<QObjectData> d_ptr;
-
- }
QObject的构造函数如下:
- QObject::QObject(QObject *parent)
- : d_ptr(new QObjectPrivate)
- {
-
- }
- QObject::QObject(QObjectPrivate &dd, QObject *parent)
- : d_ptr(&dd)
- {
-
- }
也就是QObjectData *d_ptr = new QObjectPrivate
显然QObjectPrivate 继承了 QObjectData ;
如下
- QObjectData {
- public:
- virtual ~QObjectData() = 0;
-
- };
- class Q_CORE_EXPORT QObjectPrivate : public QObjectData
- {
- Q_DECLARE_PUBLIC(QObject)
- public:
- QObjectPrivate(int version = QObjectPrivateVersion);
- virtual ~QObjectPrivate();
-
- }
看看QObject的一个方法
- QString QObject::objectName() const
- {
- Q_D(const QObject);
- return d->objectName;
- }
展开后
- QString QObject::objectName() const
- {
- QObjectPrivate * const d = d_func()
- return d->objectName;
- }
所以Qt 为我们把从 d_func() 获取 QObjectPrivate 指针的代码给封装起来了,之后就可以直接使用d->
QObject的第二个构造函数使用传入的 QObjectPrivate 对象,但它是 protected 的,也就是说,你不能在外部类中使用这个构造函数。那么这个构造函数有什么用呢?我们来看一下 QWidget 的代码:
- class QWidget : public QObject, public QPaintDevice
- {
- Q_OBJECT
- Q_DECLARE_PRIVATE(QWidget)
-
- }
QWidget 是 QObject 的子类,然后看它的构造函数:
- QWidget::QWidget(QWidgetPrivate &dd, QWidget* parent, Qt::WindowFlags f)
- : QObject(dd, 0), QPaintDevice()
- {
- Q_D(QWidget);
- QT_TRY {
- d->init(parent, f);
- } QT_CATCH(...) {
- QWidgetExceptionCleaner::cleanup(this, d_func());
- QT_RETHROW;
- }
- }
显然了QWidgetPrivate 继承了QObjectPrivate
于是我们已经明白,为什么 QWidget 中找不到 d_ptr 了,因为所有的 d_ptr 都已经在父类 QObject 中定义好了!尝试展开一下 Q_DECLARE_PRIVATE 宏,你就能够发现,它实际上把父类的 QObjectPrivate 指针偷偷地转换成了 QWidgetPrivate 的指针。
因此有如下结论:
1、在基类中定义一个protected权限的基类私有类d_ptr指针;
2、在每个派生类中用本类私有类初始化d_ptr(该私有类需要继承基类私有类),并定义d_func(),获取基类d_ptr,这个d_func()是由 Q_DECLARE_PRIVATE展开得来的 ,并将其转换为当前私有类指针;
3、在函数中使用Q_D,这样就可以使用d了;
4、在私有数据继承体系中,不要忘记将析构函数定义为虚函数,基类析构函数中释放d_ptr,以防内存泄露!!!
============================================================
Q-指针
q指针是在私有数据类中使用的,来获取主类指针。
- class Q_CORE_EXPORT QObjectPrivate : public QObjectData
- {
- Q_DECLARE_PUBLIC(QObject)
- public:
-
- };
展开后:
- class Q_CORE_EXPORT QObjectPrivate : public QObjectData
- {
- inline QObject* q_func() { return static_cast<QObject *>(q_ptr); } /
- inline const QObject* q_func() const { return static_cast<const QObject *>(q_ptr); } /
- friend class QObject;
-
- }
QObjectData定义如下:
- QObjectData {
- public:
- QObject *q_ptr;
-
- }
- #define Q_Q(QObject) QObject * const q = q_func()
三、使用的例子:
在使用调色板中
- void QWidget::setPalette(const QPalette &palette)
- {
- Q_D(QWidget);
- setAttribute(Qt::WA_SetPalette, palette.resolve() != 0);
- QPalette naturalPalette = d->naturalWidgetPalette(d->inheritedPaletteResolveMask);
- QPalette resolvedPalette = palette.resolve(naturalPalette);
- d->setPalette_helper(resolvedPalette);
- }
- void QWidgetPrivate::setPalette_helper(const QPalette &palette)
- {
- Q_Q(QWidget);
- if (data.pal == palette && data.pal.resolve() == palette.resolve())
- return;
- data.pal = palette;
- updateSystemBackground();
- propagatePaletteChange();
- updateIsOpaque();
- q->update();
- updateIsOpaque();
- }
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Q_DECLARE_PRIVATE与Q_DECLARE_PUBLIC
这两个宏在Qt的源码中随处可见,重要性不言而喻。在 部落格的 Inside Qt Series 系列文章中,他用了3篇文章来讲这个问题。
因为 QObject 本身比较复杂,这两个宏和一个复杂的东西搅和到一块,还真是不好理解。不过幸好,这个两个宏和QObject 没有必然的联系。故接下来,忘记 QObject,看一个普通的C++的类
例子
类 QtServiceController 定义:
class QtServiceController
{
Q_DECLARE_PRIVATE(QtServiceController)
public:
QtServiceController(const QString &name);
//省略其他
private:
QtServiceControllerPrivate *d_ptr;
};
宏定义
宏定义在 QtGlobal(即qglobal.h)头文件中:
#define Q_DECLARE_PRIVATE(Class) \
inline Class##Private* d_func() { return reinterpret_cast<Class##Private *>(qGetPtrHelper(d_ptr)); } \
inline const Class##Private* d_func() const { return reinterpret_cast<const Class##Private *>(qGetPtrHelper(d_ptr)); } \
friend class Class##Private;
#define Q_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;
这两个宏在这看起来真蛮绕的,因为这个例子太简单了,两个宏的威力发挥不出来。反正核心就是:
在 QtServiceController 中通过 d_func() 可以获得 QtServiceControllerPrivate 的指针 d_ptr
在 QtServiceControllerPrivate 中通过 q_func() 可以获得 QtServiceController 的指针 q_ptr
Q_D 与 Q_Q
这是另两个Qt源码中随处可见的宏,那么它们有什么用呢?
#define Q_D(Class) Class##Private * const d = d_func()
#define Q_Q(Class) Class * const q = q_func()
两个宏展开后分别是对 d_func 和 q_func 两个函数的调用,返回值分别赋值给 d 和 q 两个指针变量。
于是:
在 QtServiceController 中的成员函数中,我们只需要添加 Q_D(QtServiceController) 宏,在该函数内就可以直接用 d 来指代 d_ptr
在 QtServiceControllerPrivate 中的成员函数中,我们只需要添加 Q_Q(QtServiceController)宏,在该函数内就可以直接用 q 来指代 q_ptr
d_ptr与q_ptr
绕这么大圈,为什么不直接用 d_ptr 与 q_ptr 呢。在,在我们的例子中,确实可以直接用,而且会更直接更简单。官方这么用了,或许是为了和其他类保持一致吧。
但在其他情况下,这么做显然是有意义的,因为 d_ptr 与 d,q_ptr 与 q 的类型并不一致(比如QObject系列)。这也是为何宏展开后有cast的原因
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
下面在上一篇的基础上,我们进入Qt的源代码,看看Qt4.x是如何实现 Private Classes 的。
正如前面我们说的,或许你会看到很多类似 Q_D 或者 Q_Q 这类的宏。那么,我们来试着看一下这样的代码:
void MyClass::setFoo( int i )
{
Q_D(MyClass);
d->m_foo = i;
}
int MyClass::foo() const
{
Q_D(const MyClass);
return d->m_foo;
}
按照传统 C++ 的类,如果我们要实现这样的 getter 和 setter,我们应该使用一个私有变量 _i,然后操作这个变量。按照上一篇说的 Private Class 的做法,我们就要建一个 MyClassPrivateData 这样的类,然后使用指针对所有的数据操作进行委托。
再来看一个比较 Qt 的例子:
class MyObject: public QObject
{
Q_OBJECT
public:
MyObject();
virtual ~ MyObject();
void setMemberX( int x );
int memberX() const;
void setMemberY( double y);
double memberY() const;
signals:
void priorityChanged( MyObject::Priority priority );
private:
int m_memberX;
double m_memberY;
};
在来看一下 Qt 的实现:
class MyObjectPrivate;
class MyObject: public QObject
{
Q_OBJECT
public:
MyObject();
virtual ~ MyObject();
void setMemberX( int x );
int memberX() const;
void setMemberY( double y);
double memberY() const;
signals:
void priorityChanged( MyObject::Priority priority );
protected:
MyObjectPrivate * const d_ptr;
private:
Q_DECLARE_PRIVATE(MyObject);
};
这个例子很简单,一个使用传统方法实现,另一个采用了 Qt4.x 的方法。Qt4.x 的方法被称为 D-Pointer,因为它会使用一个名为 d 的指针,正如上面写的那个 d_ptr。使用传统方法,我们需要在 private 里面写上所有的私有变量,通常这会让整个文件变得很长,更为重要的是,用户并不需要这些信息。而使用 D-Pointer 的方法,我们的接口变得很漂亮:再也没有那一串长长的私有变量了。你不再需要将你的私有变量一起发布出去,它们就在你的 d 指针里面。如果你要修改数据类型这些信息,你也不需要去修改头文件,只需改变私有数据类即可。
需要注意的一点是,与单纯的 C++ 类不同,如果你的私有类需要定义 signals 和 slots,就应该把这个定义放在头文件中,而不是像上一篇所说的放在 cpp 文件中。这是因为 qmake 只会检测 .h 文件中的 Q_OBJECT 宏
(这一点大家务必注意)。当然,你不应该把这样的 private class 放在你的类的同一个头文件中,因为这样做的话就没有意义了。常见做法是,定义一个 private 的头文件,例如使用 myclass_p.h 的命名方式(这也是 Qt 的命名方式)。并且记住,不要把 private 头文件放到你发布的 include 下面!因为这不是你发布的一部分,它们是私有的。然后,在你的 myclass 头文件中,使用
class MyClassPrivate;
这种前向声明而不是直接
#include "myclass_p.h"
这种方式。这也是为了避免将私有的头文件发布出去,并且前向声明可以缩短编译时间。
在这个类的 private 部分,我们使用了一个 MyClassPrivate 的 const 指针 d_ptr。如果你需要让这个类的子类也能够使用这个指针,就应该把这个 d_ptr 放在 protected 部分,正如上面的代码那样。并且,我们还加上了 const 关键字,来确保它只能被初始化一次。
下面,我们遇到了一个神奇的宏:Q_DECLARE_PRIVATE。这是干什么用的?那么,我们先来看一下这个宏的展开:
#define Q_DECLARE_PRIVATE(Class) \
inline Class##Private* d_func() { return reinterpret_cast(qGetPtrHelper(d_ptr)); } \
inline const Class##Private* d_func() const { return reinterpret_cast(qGetPtrHelper(d_ptr)); } \
friend class Class##Private;
如果你看不大懂,那么就用我们的 Q_DECLARE_PRIVATE(MyClass) 看看展开之后是什么吧:
inline MyClassPrivate* d_func() { return reinterpret_cast(qGetPtrHelper(d_ptr)); }
inline const MyClassPrivate* d_func() const { return reinterpret_cast(qGetPtrHelper(d_ptr)); }
friend class MyClassPrivate;
它实际上创建了两个 inline 的 d_func() 函数,返回值分别是我们的 d_ptr 指针和 const 指针。另外,它还把 MyClassPrivate 类声明为 MyClass 的 friend。这样的话,我们就可以在 MyClass 这个类里面使用 Q_D(MyClass) 以及 Q_D(const MyClass)。还记得我们最先看到的那段代码吗?现在我们来看看这个 Q_D 倒是是何方神圣!
// A template function for getting the instance to your private class instance.
template static inline T *qGetPtrHelper(T *ptr) { return ptr; }
// A macro for getting the d-pointer
#define Q_D(Class) Class##Private * const d = d_func()
下面还是自己展开一下这个宏,就成了
MyClassPrivate * const d = d_func()
简单来说,Qt 为我们把从 d_func() 获取 MyClassPrivate 指针的代码给封装起来了,这样我们就可以比较面向对象的使用 getter 函数获取这个指针了。
现在我们已经比较清楚的知道 Qt 是如何使用 D-Pointer 实现我们前面所说的信息隐藏的了。但是,还有一个问题:如果我们把大部分代码集中到 MyClassPrivate 里面,很可能需要让 MyClassPrivate 的实现访问到 MyClass 的一些东西。现在我们让主类通过 D-Pointer 访问 MyClassPrivate 的数据,但是怎么反过来让 MyClassPrivate 访问主类的数据呢?Qt 也提供了相应的解决方案,那就是 Q_Q 宏,例如:
class MyObjectPrivate
{
public:
MyObjectPrivate(MyObject * parent):
q_ptr( parent ),
m_priority(MyObject::Low)
{}
void foo()
{
// Demonstrate how to make MyObject to emit a signal
Q_Q(MyObject);
emit q->priorityChanged( m_priority );
}
// Members
MyObject * const q_ptr;
Q_DECLARE_PUBLIC(MyObject);
MyObject::Priority m_priority;
};
在 private 类 MyObjectPrivate 中,通过构造函数将主类 MyObject 的指针传给 q_ptr。然后我们使用类似主类中使用的 Q_DECLARE_PRIVATE 的宏一样的另外的宏 Q_DECLARE_PUBLIC。这个宏所做的就是让你能够通过 Q_Q(Class) 宏使用主类指针。与 D-Pointer 不同,这时候你需要使用的是 Q_Pointer。这两个是完全相对的,这里也就不再赘述。
现在我们已经能够使用比较 Qt 的方式来使用 Private Classes 实现信息隐藏了。这不仅仅是 Qt 的实现,当然,你也可以不用 Q_D 和 Q_Q,而是使用自己的方式,这些都无关紧要。最主要的是,我们了解了一种 C++ 类的设计思路,这是 Qt 的源代码教给我们的。
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
QObject中的d_ptr是这样定义的
QScopedPointer<QObjectData> d_ptr;
其中QScopedPointer定义如下:
template <typename T, typename Cleanup = QScopedPointerDeleter<T> >
class QScopedPointer
{
typedef T *QScopedPointer:: *RestrictedBool;
public:
explicit inline QScopedPointer(T *p = 0) : d(p)
{
}
inline ~QScopedPointer()
{
T *oldD = this->d;
Cleanup::cleanup(oldD);
}
inline T &operator*() const
{
Q_ASSERT(d);
return *d;
}
inline T *operator->() const
{
Q_ASSERT(d);
return d;
}
inline bool operator!() const
{
return !d;
}
#if defined(Q_QDOC)
inline operator bool() const
{
return isNull() ? 0 : &QScopedPointer::d;
}
#else
inline operator RestrictedBool() const
{
return isNull() ? 0 : &QScopedPointer::d;
}
#endif
inline T *data() const
{
return d;
}
inline bool isNull() const
{
return !d;
}
inline void reset(T *other = 0)
{
if (d == other)
return;
T *oldD = d;
d = other;
Cleanup::cleanup(oldD);
}
inline T *take()
{
T *oldD = d;
d = 0;
return oldD;
}
inline void swap(QScopedPointer<T, Cleanup> &other)
{
qSwap(d, other.d);
}
typedef T *pointer;
protected:
T *d;
private:
Q_DISABLE_COPY(QScopedPointer)
};
QObjectData定义如下:
class Q_CORE_EXPORT QObjectData {
public:
virtual ~QObjectData() = 0;
QObject *q_ptr;
QObject *parent;
QObjectList children;
uint isWidget : 1;
uint blockSig : 1;
uint wasDeleted : 1;
uint isDeletingChildren : 1;
uint sendChildEvents : 1;
uint receiveChildEvents : 1;
uint isWindow : 1; //for QWindow
uint unused : 25;
int postedEvents;
QDynamicMetaObjectData *metaObject;
QMetaObject *dynamicMetaObject() const;
};
QScopedPointer是一个模版类,传入QObjectData参数,在QObject的类定义中,有这么一句话:
inline QObject *parent() const { return d_ptr->parent; }
d_ptr是QScopedPointer对象,如何有parent成员呢?
d_ptr并不是一个指针,只是一个变量而已,并不能写->,一开始没有发现这个问题,那为什么可以写成->呢,如果开始考虑这个问题,那就已经接近答案了,QScopedPointer重载了->操作符,返回了d指针,而d指针就是QObjectData类型的,所以可以写成d_ptr->parent,这里展开就是这样的形式d_ptr->()->parent,最后一次还是要调用内置的->操作符。这就是箭头操作符的重载。
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
QScopedPointer
就分配空间和释放空间而言,Qt的处理有点乏味,要不然是通过隐式共享的containers,要不然就是通过QObject的父子关系模式。但总有些时候我们需要在堆上分配一些空间,问题来了,我们该在哪里delete它,如何能够确保不产生内存泄露呢?
QScopedPointer就为了解决这个问题而生的,哈哈 QScopedPointer在其生命期结束后会自动删除它所指的对象。
void foo()
{
QScopedPointer<int> i(new int(42));
…
if (someCondition)
return; // 我们在堆上构造的整数这时会在这里删除或者是在下面
…
} // … 也可能在这里
复制代码
这样就可以确保我们在堆上为整数42分配的空间不会产生内存泄露,同时我们也不用手动delete它,哈哈。
那我们如何访问QScopedPointer 所指的对象呢?QScopedPointer重新实现了operator* 和operator->,因此我们可以像下面这样使用它:
QScopedPointer<int> i(new int(42));
*i = 43;
复制代码
有些运算符是不支持的,如赋值运算符:
QScopedPointer<int> i(new int(42));
i = new int(43); // 编译不通过
i.reset(new int(43)); // 正确
复制代码
operator T*()也是没有的:
int *foo()
{
QScopedPointer<int> i(new int(42));
…
return i; // thankfully, this does not compile.
}
复制代码
看到错误没?在上面的代码中一旦我们return,我们构造的对象将被删除因为i的生命期已经结束,我们将会返回一个野指针,这可能会导致崩溃。如果真要返回我们应该像下面这样:
int *foo()
{
QScopedPointer<int> i(new int(42));
…
if (someError)
return 0; // our integer is deleted here
return i.take(); // from now on, our heap object is on its own.
}
复制代码
通过调用take()方法,我们告诉QScopedPointer它的工作已经做完,现在由我们来接管在堆上分配对象的所有权,哈哈
上面的只是针对new而言的,那么如果是malloc或者operator new[]构造数组呢?这里我们需要使用QScopedPointer的第二个参数:
QScopedPointer<int, QScopedPointerPodDeleter> pod(static_cast<int *>(malloc(sizeof int)));
复制代码
但QScopedPointer生命期结束后QScopedPointerPodDeleter (pod 是 “plain old data”的缩写) 会调用free来释放我们分配的空间。
为了方便我们有一个专门针对数组的类,QScopedArrayPointer,在其生命期结束后会自动调用delete[]方法:
void foo()
{
QScopedArrayPointer<int> i(new int[10]);
i[2] = 42;
…
return; // our integer array is now deleted using delete[]
}
复制代码
注意如果你有一个引用计数的对象,可以使用QExplicitlySharedDataPointer来确保当其引用计数为0时正确删除。
在Qt的S60分支中,QScopedPointe 和QExplicitlySharedDataPointer已经得到了广泛的使用。相信不久就可以加入Qt的总分支中。通过使用Qt的这些智能指针,我们可以让我们的程序更易读同时也不用过于担心,因为这些方法都是inline内联的。
经常这么用
class MyPrivateClass; // forward declare MyPrivateClass
class MyClass
{
private:
QScopedPointer<MyPrivateClass> privatePtr; // QScopedPointer to forward declared class
public:
MyClass(); // OK
inline ~MyClass() {} // VIOLATION - Destructor must not be inline
private:
Q_DISABLE_COPY(MyClass) // OK - copy constructor and assignment operators
// are now disabled, so the compiler won't implicitely
// generate them.
};
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
======================================================================================================================
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
malloc 的实现涉及物理内存,虚拟内存?
malloc返回返回请求大小的连续内存空间,如果申请失败,返回错误。malloc的实现跟物理内存有关系吗?涉及虚拟内存吗?
111111111111111111111111111111111111111111111111111
malloc是一个库函数,不同的操作系统上具体实现细节是不同的,以下就以linux条件下进行分析:linux采用的是glibc中堆内存管理ptmalloc实现,虚拟内存的布局规定了malloc申请位置以及大小,malloc一次性能申请小内存(小于128KB),分配的是在堆区(heap),用sbrk()进行对齐生长,而malloc一次性申请大内存(大于128KB时)分配到的是在映射区,而不是在堆区,采用的mmap()系统调用进行映射。当然虚拟地址只是规定了一种最理想的状态,实际分配还是要考虑到物理内存加交换内存总量的限制,因为每次分配,特别是大内存分配采用mmap()映射内存需要记录物理内存加交换内存地址,所有物理内存加交换内存限制了malloc实际分配。比如32位情况下,最新版本的linux的映射区在用户空间区的3G位置,而映射区向下生长,所以理想情况下大概能有2.9GB(除去开始地址128M),如果你的物理内存加交换区只有2G,malloc一次申请最多1.8G左右,如果你的物理内存加交换区大于4G,那么最多能有2.9G或者2.8G左右。网上能找到测试代码的。
2222222222222222222222222222222222222222222222
malloc的实现与物理内存自然是无关的,内核为每个进程维护一张页表,页表存储进程空间内每页的虚拟地址,页表项中有的虚拟内存页对应着某个物理内存页面,也有的虚拟内存页没有实际的物理页面对应。无论malloc通过sbrk还是mmap实现,分配到的内存只是虚拟内存,而且只是虚拟内存的页号,代表这块空间进程可以用,实际上还没有分配到实际的物理页面。等你的进程访问到这个新分配的内存空间的时候,如果其还没有对应的物理页面分配,就会产生缺页中断,内核这个时候会给进程分配实际的物理页面,以与这个未被映射的虚拟页面对应起来,然后程序就可以欢快的继续往下跑了。
3333333333333333333333333333333333
最近也看了一些内存管理的文章。malloc的实现我觉的题主不妨可以这样理解。对于32位操作系统,每一个进程都有32bit的虚拟地址空间。当malloc的时候,随便从自己的4G的虚拟地址空间里面拿出来一块这时候并没有真的物理地址给分配出来,从上面Target的验证结果来看也是这样。然后当你拿到这个虚拟地址准备读写的时候就会产生缺页中断,然后系统调用缺页中断函数把你要访问的虚拟地址给分配一个物理内存地址,这样你就可以使用了。
44444444444444444444444444444444444
要回答这个问题,首先要介绍一点操作系统内存管理的基础:
所有运行在现代操作系统上的应用程序所得到的内存地址都是虚拟地址,都需要通过操作系统和硬件层面的地址翻译机制才能对应到物理内存的实际地址。物理内存实际被分为了若干区块,被称为“页”。操作系统的任务是将这些页分配给不同进程,而分配的方式就是在这些进程请求访问和写入某个虚拟地址的时候,把这个虚拟地址所属的区域对应到物理地址里的某一页,以此完成地址翻译。
基于上述事实,一个典型的malloc函数实现通常包含一个底部虚拟地址(可以使用系统调用mmap)和一小部分初始空间(可以使用系统调用sbrk获得更多空间)。操作系统会负责这些虚拟地址到物理地址的对应。sbrk没有办法获得连续空间?其实这个问题不存在,虚拟地址里两个连续的区域可能已经被映射到了物理地址里两块不连续的页了。
虚拟内存是一个需要几十分钟才能理解清楚的概念。其中的机制远比“物理内存不够了存到硬盘里”复杂。详情可以参考《深入理解计算机系统》。
55555555555555555555555555555555555555555555
今天在64位机器上测试一下最大能申请到多大内存。
系统:ubuntu 12.04 64位
机器:core i3, 内存 4G
执行代码:
size_t size = 2000000000; //20亿
int *pointer = (int*) malloc(sizeof(size)*int);
执行,申请失败。
把内存换成 8G
执行代码:
size_t size = 2500000000; //25亿
int *pointer = (int*) malloc(sizeof(size)*int);
执行,无压力过了...
泪奔,求解~
66666666666666666666666666666666666666666
在设计一个软件系统的时候,要非常注意信息隐藏。
在这方面的一个例子就是解决方案的分层处理。
malloc就是一个很好的例子。
1.malloc的算法仅仅建立在应用程序编译选项的全局堆尺寸上,所以它的算法应对的是一个一维的连续内存区域。其他的细节对malloc函数都是透明的。
2.malloc与应用程序的接口仅仅是一个void *指针,这同样体现了信息隐藏的原则。内存块的大小是多少,并没有体现出来,这些已经封装在malloc与free的实现内部了。
3.用户仅仅发出一个内存分配请求,它不需要知道与内存分配相关的任何细节。
这是一个非常好的设计。
77777777777777777777777777777777777777777777
无关,malloc是运行时库提供的。内存管理及转储到磁盘(也就是虚拟内存)是操作系统干得
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
下面在上一篇的基础上,我们进入Qt的源代码,看看Qt4.x是如何实现 Private Classes 的。
正如前面我们说的,或许你会看到很多类似 Q_D 或者 Q_Q 这类的宏。那么,我们来试着看一下这样的代码:
void MyClass::setFoo( int i )
{
Q_D(MyClass);
d->m_foo = i;
}
int MyClass::foo() const
{
Q_D(const MyClass);
return d->m_foo;
}
按照传统 C++ 的类,如果我们要实现这样的 getter 和 setter,我们应该使用一个私有变量 _i,然后操作这个变量。按照上一篇说的 Private Class 的做法,我们就要建一个 MyClassPrivateData 这样的类,然后使用指针对所有的数据操作进行委托。
再来看一个比较 Qt 的例子:
class MyObject: public QObject
{
Q_OBJECT
public:
MyObject();
virtual ~ MyObject();
void setMemberX( int x );
int memberX() const;
void setMemberY( double y);
double memberY() const;
signals:
void priorityChanged( MyObject::Priority priority );
private:
int m_memberX;
double m_memberY;
};
在来看一下 Qt 的实现:
class MyObjectPrivate;
class MyObject: public QObject
{
Q_OBJECT
public:
MyObject();
virtual ~ MyObject();
void setMemberX( int x );
int memberX() const;
void setMemberY( double y);
double memberY() const;
signals:
void priorityChanged( MyObject::Priority priority );
protected:
MyObjectPrivate * const d_ptr;
private:
Q_DECLARE_PRIVATE(MyObject);
};
这个例子很简单,一个使用传统方法实现,另一个采用了 Qt4.x 的方法。Qt4.x 的方法被称为 D-Pointer,因为它会使用一个名为 d 的指针,正如上面写的那个 d_ptr。使用传统方法,我们需要在 private 里面写上所有的私有变量,通常这会让整个文件变得很长,更为重要的是,用户并不需要这些信息。而使用 D-Pointer 的方法,我们的接口变得很漂亮:再也没有那一串长长的私有变量了。你不再需要将你的私有变量一起发布出去,它们就在你的 d 指针里面。如果你要修改数据类型这些信息,你也不需要去修改头文件,只需改变私有数据类即可。
需要注意的一点是,与单纯的 C++ 类不同,如果你的私有类需要定义 signals 和 slots,就应该把这个定义放在头文件中,而不是像上一篇所说的放在 cpp 文件中。这是因为 qmake 只会检测 .h 文件中的 Q_OBJECT 宏
(这一点大家务必注意)。当然,你不应该把这样的 private class 放在你的类的同一个头文件中,因为这样做的话就没有意义了。常见做法是,定义一个 private 的头文件,例如使用 myclass_p.h 的命名方式(这也是 Qt 的命名方式)。并且记住,不要把 private 头文件放到你发布的 include 下面!因为这不是你发布的一部分,它们是私有的。然后,在你的 myclass 头文件中,使用
class MyClassPrivate;
这种前向声明而不是直接
#include "myclass_p.h"
这种方式。这也是为了避免将私有的头文件发布出去,并且前向声明可以缩短编译时间。
在这个类的 private 部分,我们使用了一个 MyClassPrivate 的 const 指针 d_ptr。如果你需要让这个类的子类也能够使用这个指针,就应该把这个 d_ptr 放在 protected 部分,正如上面的代码那样。并且,我们还加上了 const 关键字,来确保它只能被初始化一次。
下面,我们遇到了一个神奇的宏:Q_DECLARE_PRIVATE。这是干什么用的?那么,我们先来看一下这个宏的展开:
#define Q_DECLARE_PRIVATE(Class) \
inline Class##Private* d_func() { return reinterpret_cast(qGetPtrHelper(d_ptr)); } \
inline const Class##Private* d_func() const { return reinterpret_cast(qGetPtrHelper(d_ptr)); } \
friend class Class##Private;
如果你看不大懂,那么就用我们的 Q_DECLARE_PRIVATE(MyClass) 看看展开之后是什么吧:
inline MyClassPrivate* d_func() { return reinterpret_cast(qGetPtrHelper(d_ptr)); }
inline const MyClassPrivate* d_func() const { return reinterpret_cast(qGetPtrHelper(d_ptr)); }
friend class MyClassPrivate;
它实际上创建了两个 inline 的 d_func() 函数,返回值分别是我们的 d_ptr 指针和 const 指针。另外,它还把 MyClassPrivate 类声明为 MyClass 的 friend。这样的话,我们就可以在 MyClass 这个类里面使用 Q_D(MyClass) 以及 Q_D(const MyClass)。还记得我们最先看到的那段代码吗?现在我们来看看这个 Q_D 倒是是何方神圣!
// A template function for getting the instance to your private class instance.
template static inline T *qGetPtrHelper(T *ptr) { return ptr; }
// A macro for getting the d-pointer
#define Q_D(Class) Class##Private * const d = d_func()
下面还是自己展开一下这个宏,就成了
MyClassPrivate * const d = d_func()
简单来说,Qt 为我们把从 d_func() 获取 MyClassPrivate 指针的代码给封装起来了,这样我们就可以比较面向对象的使用 getter 函数获取这个指针了。
现在我们已经比较清楚的知道 Qt 是如何使用 D-Pointer 实现我们前面所说的信息隐藏的了。但是,还有一个问题:如果我们把大部分代码集中到 MyClassPrivate 里面,很可能需要让 MyClassPrivate 的实现访问到 MyClass 的一些东西。现在我们让主类通过 D-Pointer 访问 MyClassPrivate 的数据,但是怎么反过来让 MyClassPrivate 访问主类的数据呢?Qt 也提供了相应的解决方案,那就是 Q_Q 宏,例如:
class MyObjectPrivate
{
public:
MyObjectPrivate(MyObject * parent):
q_ptr( parent ),
m_priority(MyObject::Low)
{}
void foo()
{
// Demonstrate how to make MyObject to emit a signal
Q_Q(MyObject);
emit q->priorityChanged( m_priority );
}
// Members
MyObject * const q_ptr;
Q_DECLARE_PUBLIC(MyObject);
MyObject::Priority m_priority;
};
在 private 类 MyObjectPrivate 中,通过构造函数将主类 MyObject 的指针传给 q_ptr。然后我们使用类似主类中使用的 Q_DECLARE_PRIVATE 的宏一样的另外的宏 Q_DECLARE_PUBLIC。这个宏所做的就是让你能够通过 Q_Q(Class) 宏使用主类指针。与 D-Pointer 不同,这时候你需要使用的是 Q_Pointer。这两个是完全相对的,这里也就不再赘述。
现在我们已经能够使用比较 Qt 的方式来使用 Private Classes 实现信息隐藏了。这不仅仅是 Qt 的实现,当然,你也可以不用 Q_D 和 Q_Q,而是使用自己的方式,这些都无关紧要。最主要的是,我们了解了一种 C++ 类的设计思路,这是 Qt 的源代码教给我们的。
TTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTT
TTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTT
TTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTT
[Qt] QScopedPointer介绍
QScopedPointer
就分配空间和释放空间而言,Qt的处理有点乏味,要不然是通过隐式共享的containers,要不然就是通过QObject的父子关系模式。但总有些时候我们需要在堆上分配一些空间,问题来了,我们该在哪里delete它,如何能够确保不产生内存泄露呢?
QScopedPointer就为了解决这个问题而生的,哈哈 QScopedPointer在其生命期结束后会自动删除它所指的对象。
- void foo()
- {
- QScopedPointer<int> i(new int(42));
- …
- if (someCondition)
- return; // 我们在堆上构造的整数这时会在这里删除或者是在下面
- …
- } // … 也可能在这里
复制代码
这样就可以确保我们在堆上为整数42分配的空间不会产生内存泄露,同时我们也不用手动delete它,哈哈。
那我们如何访问QScopedPointer 所指的对象呢?QScopedPointer重新实现了operator* 和operator->,因此我们可以像下面这样使用它:
- QScopedPointer<int> i(new int(42));
- *i = 43;
复制代码
有些运算符是不支持的,如赋值运算符:
- QScopedPointer<int> i(new int(42));
- i = new int(43); // 编译不通过
- i.reset(new int(43)); // 正确
复制代码
operator T*()也是没有的:
- int *foo()
- {
- QScopedPointer<int> i(new int(42));
- …
- return i; // thankfully, this does not compile.
- }
复制代码
看到错误没?在上面的代码中一旦我们return,我们构造的对象将被删除因为i的生命期已经结束,我们将会返回一个野指针,这可能会导致崩溃。如果真要返回我们应该像下面这样:
- int *foo()
- {
- QScopedPointer<int> i(new int(42));
- …
- if (someError)
- return 0; // our integer is deleted here
- return i.take(); // from now on, our heap object is on its own.
- }
复制代码
通过调用take()方法,我们告诉QScopedPointer它的工作已经做完,现在由我们来接管在堆上分配对象的所有权,哈哈
上面的只是针对new而言的,那么如果是malloc或者operator new[]构造数组呢?这里我们需要使用QScopedPointer的第二个参数:
- QScopedPointer<int, QScopedPointerPodDeleter> pod(static_cast<int *>(malloc(sizeof int)));
复制代码
但QScopedPointer生命期结束后QScopedPointerPodDeleter (pod 是 “plain old data”的缩写) 会调用free来释放我们分配的空间。
为了方便我们有一个专门针对数组的类,QScopedArrayPointer,在其生命期结束后会自动调用delete[]方法:
- void foo()
- {
- QScopedArrayPointer<int> i(new int[10]);
- i[2] = 42;
- …
- return; // our integer array is now deleted using delete[]
- }
复制代码
注意如果你有一个引用计数的对象,可以使用QExplicitlySharedDataPointer来确保当其引用计数为0时正确删除。
在Qt的S60分支中,QScopedPointe 和QExplicitlySharedDataPointer已经得到了广泛的使用。相信不久就可以加入Qt的总分支中。通过使用Qt的这些智能指针,我们可以让我们的程序更易读同时也不用过于担心,因为这些方法都是inline内联的。
经常这么用
class MyPrivateClass; // forward declare MyPrivateClass
class MyClass
{
private:
QScopedPointer<MyPrivateClass> privatePtr; // QScopedPointer to forward declared class
public:
MyClass(); // OK
inline ~MyClass() {} // VIOLATION - Destructor must not be inline
private:
Q_DISABLE_COPY(MyClass) // OK - copy constructor and assignment operators
// are now disabled, so the compiler won't implicitely
// generate them.
};
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
(1)深拷贝:即就是生成对象的一个完整的复制品;
(2)浅拷贝:只是一个引用复制(比如仅仅复制指向共享数据的指针)。
(3)隐式共享:也叫做回写复制(copy on write)。
Qt中的很多C++类通过使用隐式数据共享来最大化资源的使用效率和最小化复制的资源耗费。将隐
式共享类作为参数传递不仅安全而且效率高,因为在这个过程中只有指向这个数据的指针被传递,
并且当且仅当有函数对这个数据进行写操作时,才会对该数据进行复制。
隐式共享可以降低对内存和CPU资源的使用,提高程序的运行效率。使用隐式共享能使得在函数中(eg. 参数、返回值)使用值传递更有效率。 QString采用隐式共享技术,将深拷贝和浅拷贝很好地结合了起来。
举例说明饮食共享是如何工作的:
- QString str1 = "ubuntu";
- QString str2 = str1;//str2 = "ubuntu"
- str2[2] = "m";//str2 = "ubmntu",str1 = "ubuntu"
- str2[0] = "o";//str2 = "obmntu",str1 = "ubuntu"
- str1 = str2;//str1 = "obmntu",
line1: 初始化一个内容为"ubuntu"的字符串;
line2: 将字符串对象str1赋值给另外一个字符串str2(由QString的拷贝构造函数完成str2的初始化)。
在对str2赋值的时候,会发生一次浅拷贝,导致两个QString对象都会指向同一个数据结构。该数据结构除了保存字符串“ubuntu”之外,还保存一个引用计数器,用来记录字符串数据的引用次数。此处,str1和str2都指向同一数据结构,所以此时引用计数器的值为2.
line3: 对str2做修改,将会导致一次深拷贝,使得对象str2指向一个新的、不同于str1所指的数据结构(该数据结构中引用计数器值为1,只有str2是指向该结构的),同时修改原来的、str1所指向的数据结构,设置它的引用计数器值为1(此时只有str1对象指向该结构);并在这个str2所指向的、新的数据结构上完成数据的修改。引用计数为1就意味着该数据没有被共享。
line4: 进一步对str2做修改,不过不会引起任何形式的拷贝,因为str2所指向的数据结构没有被共享。
line5: 将str2赋给str1.此时,str1修改它指向的数据结构的引用计数器的值位0,表示没有QString类的对象再使用这个数据结构了;因此str1指向的数据结构将会从从内存中释放掉;这一步操作的结构是QString对象str1和str2都指向了字符串为“obmntu”的数据结构,该结构的引用计数为2.
Qt中支持引用计数的类有很多(QByteArray, QBrush, QDir, QBitmap... ...).
再比如:
- QList<QString> list1;
- list1 << "helianthus";
- QList<QString> list2 = list1;
-
- cout << &list1[0] << endl;
- cout << &list2[0] << endl;
在这个例子中使用了[]运算子,list1和list2中的数据结构经过了复制,所以并不是共享的。因此最后显示的两个记忆体位置并不相同,但是使用了at()时的情况是一样的:
- QList<QString> list1;
- list1 << "x";
- QList<QString> list2 = list1;
- //QList::at(int i) const:Returns the item at index position i in the list. i must be a
- //valid index position in the list (i.e., 0 <= i < size())
- cout << &(list1.at(0)) << endl;
- cout << &(list2.at(0)) < < endl;
所以,在只读的情况下,使用at()方法要比使用[]运算子效率高,因为省去了数据结构的复制成本。
隐式共享之所以称为copy on write,也就是说只要容器中的数据结构内容发生了变化就不再共享,而要复制。就上一个例子而言,如果对list2做了修改,比如:
那么:
- cout << &(list1.at(0)) << endl;
- cout << &(list2.at(0)) << endl;
此时list1和list2显示的记忆体位置就是不同的。
再比如:
- QList<int> function() {
- QList<int> list;
- // ...blah..blah
- cout << &list << endl;
- return list;
- }
然后,就可以利用这个函数实现数据共享,又不会数据结构复制:
- QList<int> retval= function();
- cout << &retval << endl;
在上面这个代码片段中,function()中的list位置和接收function中的list的retval
位置是相同的。总的来说,QT中所有的容器类都支持隐式共享;此外,QByteArray,QBrush,QPen,QPalette,QBitmap,QImage,QPixmap,QCursor,QDir,QFont和QVariant等也都只是隐式共享机制。
而无论是Java风格还是STL风格的迭代器,使用只读迭代器时,背后也都使用到了隐式共享机制,以增加读取的效率。
ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ
ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ
ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ
1.浅拷贝:
浅拷贝就比如像引用类型
浅拷贝是指源对象与拷贝对象共用一份实体,仅仅是引用的变量不同(名称不同)。对其中任何一个对象的改动都会影响另外一个对象。举个例子,一个人一开始叫张三,后来改名叫李四了,可是还是同一个人,不管是张三缺胳膊少腿还是李四缺胳膊少腿,都是这个人倒霉。
2.深拷贝:
而深拷贝就比如值类型。
深拷贝是指源对象与拷贝对象互相独立,其中任何一个对象的改动都不会对另外一个对象造成影响。举个例子,一个人名叫张三,后来用他克隆(假设法律允许)了另外一个人,叫李四,不管是张三缺胳膊少腿还是李四缺胳膊少腿都不会影响另外一个人。比较典型的就是Value(值)对象,如预定义类型Int32,Double,以及结构(struct),枚举(Enum)等。
3.隐式共享:
隐式共享又叫做回写复制。当两个对象共享同一份数据时(通过浅拷贝实现数据块的共享),如果数据不改变,不进行数据的复制。而当某个对象需要改变数据时则执行深拷贝。
QString类采用隐式共享技术,将深拷贝和浅拷贝有机地结合起来。
例如:
void MainWindow::on_pushButton_8_clicked()
{
QString str1="data";
qDebug() << " String addr = " << &str1 <<", "<< str1.constData();
QString str2=str1; //浅拷贝指向同一个数据块
qDebug() << " String addr = " << &str2 <<", "<< str2.constData();
str2[3]='e'; //一次深拷贝,str2对象指向一个新的、不同于str1所指向的数据结构
qDebug() << " String addr = " << &str2 <<", "<< str2.constData();
str2[0]='f'; //不会引起任何形式的拷贝,因为str2指向的数据结构没有被共享
qDebug() << " String addr = " << &str2 <<", "<< str2.constData();
str1=str2; //str1指向的数据结构将会从内存释放掉,str1对象指向str2所指向的数据结构
qDebug() << " String addr = " << &str1 <<", "<< str1.constData();
qDebug() << " String addr = " << &str2 <<", "<< str2.constData();
}
实测输出结果如下(括号内是我的分析):
String addr = 0x28c79c , 0x14316660 (str1的指针地址,指向一个新的QSharedDataPointer,命名为data1)
String addr = 0x28c798 , 0x14316660 (str2的指针地址,指向前面同一个QSharedDataPointer,其实就是data1)
String addr = 0x28c798 , 0x1433f2a0 (str2的指针地址,指向一个新的QSharedDataPointer,命名为data2)
String addr = 0x28c798 , 0x1433f2a0 (str2的指针地址,指向data2,但是修改其内容)
String addr = 0x28c79c , 0x1433f2a0 (str1的指针地址,指向data2,不修改其内容,且放弃data1,使之引用计数为零而被彻底释放)
String addr = 0x28c798 , 0x1433f2a0 (str2的指针地址,指向data2,不修改其内容)
注意,str1的地址和str1.constData()地址不是一回事。
不过新问题又来了,在调用data()函数以后,怎么好像constData的地址也变了:
void MainWindow::on_pushButton_8_clicked()
{
QString str1="data";
qDebug() << " String addr = " << &str1 <<", "<< str1.constData() << ", " << str1.data();
QString str2=str1; //浅拷贝指向同一个数据块
qDebug() << " String addr = " << &str2 <<", "<< str2.constData() << ", " << str2.data();
str2[3]='e'; //一次深拷贝,str2对象指向一个新的、不同于str1所指向的数据结构
qDebug() << " String addr = " << &str2 <<", "<< str2.constData() << ", " << str2.data();
str2[0]='f'; //不会引起任何形式的拷贝,因为str2指向的数据结构没有被共享
qDebug() << " String addr = " << &str2 <<", "<< str2.constData() << ", " << str2.data();
str1=str2; //str1指向的数据结构将会从内存释放掉,str1对象指向str2所指向的数据结构
qDebug() << " String addr = " << &str1 <<", "<< str1.constData() << ", " << str1.data();
qDebug() << " String addr = " << &str2 <<", "<< str2.constData() << ", " << str2.data();
}
输出结果:
String addr = 0x28c79c , 0x143e6660 , 0x143e6660
String addr = 0x28c798 , 0x14423020 , 0x14423020
String addr = 0x28c798 , 0x14423020 , 0x14423020
String addr = 0x28c798 , 0x14423020 , 0x14423020
String addr = 0x28c79c , 0x143e6660 , 0x143e6660
String addr = 0x28c798 , 0x14423020 , 0x14423020
原因可能是因为这两句:
1. constData()的注释:
Note that the pointer remains valid only as long as the string is not modified.
就是调用data()函数以后,string存储数据的地址被修改了
2. data()的注释:
Note that the pointer remains valid only as long as the string is not modified by other means.
For read-only access, constData() is faster because it never causes a deep copy to occur.
大概是因为调用data()函数以后,立刻就引起了深拷贝,从而存储数据的地址变化了
所以事实上,先调用constData还是先调用data,结果会有所不同:
void MainWindow::on_pushButton_8_clicked()
{
QString str1="data";
qDebug() << " String addr = " << &str1 <<", "<< str1.data() << ", " << str1.constData();
QString str2=str1; //浅拷贝指向同一个数据块
qDebug() << " String addr = " << &str2 <<", "<< str2.data() << ", " << str2.constData();
str2[3]='e'; //一次深拷贝,str2对象指向一个新的、不同于str1所指向的数据结构
qDebug() << " String addr = " << &str2 <<", "<< str2.data() << ", " << str2.constData();
str2[0]='f'; //不会引起任何形式的拷贝,因为str2指向的数据结构没有被共享
qDebug() << " String addr = " << &str2 <<", "<< str2.data() << ", " << str2.constData();
str1=str2; //str1指向的数据结构将会从内存释放掉,str1对象指向str2所指向的数据结构
qDebug() << " String addr = " << &str1 <<", "<< str1.data() << ", " << str1.constData();
qDebug() << " String addr = " << &str2 <<", "<< str2.data() << ", " << str2.constData();
}
结果(其中constData是想要的结果,值得研究的地方)。而data函数因为深拷贝的原因产生了一个数据的新地址,大概是拷贝到新的存储空间吧,而constData始终指向这个QString真正存储数据的地方:
String addr = 0x28c79c , 0x144b3598 , 0x144b3598
String addr = 0x28c798 , 0x14503cc8 , 0x144b3598
String addr = 0x28c798 , 0x14503cc8 , 0x14503cc8
String addr = 0x28c798 , 0x14503cc8 , 0x14503cc8
String addr = 0x28c79c , 0x144b3598 , 0x14503cc8
String addr = 0x28c798 , 0x14503cc8 , 0x14503cc8
要是先调用constData,后调用data,结果这下constData和data又完全一致了:
String addr = 0x28c79c , 0x146b6c28 , 0x146b6c28
String addr = 0x28c798 , 0x14653498 , 0x14653498
String addr = 0x28c798 , 0x14653498 , 0x14653498
String addr = 0x28c798 , 0x14653498 , 0x14653498
String addr = 0x28c79c , 0x146b6c28 , 0x146b6c28
String addr = 0x28c798 , 0x14653498 , 0x14653498
之所以出现这种怪问题,想了半天,觉得是因为data()和constData()写在同一句语句里的原因,编译器把全部值算出来以后,再进行打印,这样constData的值有时候就不准确了。所以最好分成两句:
void MainWindow::on_pushButton_8_clicked()
{
QString str1="data";
qDebug() << " String addr = " << &str1 <<", "<< str1.constData(); qDebug() << "new addr = " << str1.data();
QString str2=str1; //浅拷贝指向同一个数据块
qDebug() << " String addr = " << &str2 <<", "<< str2.constData(); qDebug() << "new addr = " << str2.data();
str2[3]='e'; //一次深拷贝,str2对象指向一个新的、不同于str1所指向的数据结构
qDebug() << " String addr = " << &str2 <<", "<< str2.constData(); qDebug() << "new addr = " << str2.data();
str2[0]='f'; //不会引起任何形式的拷贝,因为str2指向的数据结构没有被共享
qDebug() << " String addr = " << &str2 <<", "<< str2.constData(); qDebug() << "new addr = " << str2.data();
str1=str2; //str1指向的数据结构将会从内存释放掉,str1对象指向str2所指向的数据结构
qDebug() << " String addr = " << &str1 <<", "<< str1.constData(); qDebug() << "new addr = " << str1.data();
qDebug() << " String addr = " << &str2 <<", "<< str2.constData(); qDebug() << "new addr = " << str2.data();
}
输出结果(排版了一下,取消换行):
String addr = 0x28c70c , 0x13c06660 , new addr = 0x13c06660
String addr = 0x28c708 , 0x13c06660 , new addr = 0x13c841b8
String addr = 0x28c708 , 0x13c841b8 , new addr = 0x13c841b8
String addr = 0x28c708 , 0x13c841b8 , new addr = 0x13c841b8
String addr = 0x28c70c , 0x13c841b8 , new addr = 0x13c06660
String addr = 0x28c708 , 0x13c841b8 , new addr = 0x13c841b8
这样就又正确了,真是烦死人。后面还有没有坑不知道,今天就到这里为止吧。
参考:http://www.cnblogs.com/wiessharling/archive/2013/01/05/2845819.html
--------------------------------------------------------------------
再来一个例子:
QString str1 = "ubuntu";
QString str2 = str1;//str2 = "ubuntu"
str2[2] = "m";//str2 = "ubmntu",str1 = "ubuntu"
str2[0] = "o";//str2 = "obmntu",str1 = "ubuntu"
str1 = str2;//str1 = "obmntu",
line1: 初始化一个内容为"ubuntu"的字符串;
line2: 将字符串对象str1赋值给另外一个字符串str2(由QString的拷贝构造函数完成str2的初始化)。
在对str2赋值的时候,会发生一次浅拷贝,导致两个QString对象都会指向同一个数据结构。该数据结构除了保存字符串“ubuntu”之外,还保存一个引用计数器,用来记录字符串数据的引用次数。此处,str1和str2都指向同一数据结构,所以此时引用计数器的值为2.
line3: 对str2做修改,将会导致一次深拷贝,使得对象str2指向一个新的、不同于str1所指的数据结构(该数据结构中引用计数器值为1,只有str2是指向该结构的),同时修改原来的、str1所指向的数据结构,设置它的引用计数器值为1(此时只有str1对象指向该结构);并在这个str2所指向的、新的数据结构上完成数据的修改。引用计数为1就意味着该数据没有被共享。
line4: 进一步对str2做修改,不过不会引起任何形式的拷贝,因为str2所指向的数据结构没有被共享。
line5: 将str2赋给str1.此时,str1修改它指向的数据结构的引用计数器的值位0,表示没有QString类的对象再使用这个数据结构了;因此str1指向的数据结构将会从从内存中释放掉;这一步操作的结构是QString对象str1和str2都指向了字符串为“obmntu”的数据结构,该结构的引用计数为2.
Qt中支持引用计数的类有很多(QByteArray, QBrush, QDir, QBitmap... ...).
参考:http://blog.chinaunix.net/uid-27177626-id-3949985.html
--------------------------------------------------------------------
再来一个例子:
int main(int argc, char *argv[])
{
QList<QString> list1;
list1<<"test";
QList<QString> list2=list1;
qDebug()<<&list1.at(0);
qDebug()<<&list2.at(0);
//qDebug()<<&list1[0]; //[]运算
//qDebug()<<&list2[0]; //[]运算
list2<<"tests"; // 注意,此时list2的内容是("test", "tests")
qDebug()<<&list1.at(0);
qDebug()<<&list2.at(0); // 之所以这里的地址变得不一致,是因为它的第一项内容地址变了,但仍指向"test"字符串,这里解释的还不够清楚。
QList<QString> list=copyOnWrite();
qDebug()<<&list;
qDebug()<<&list.at(0);
}
QList<QString> copyOnWrite()
{
QList<QString> list;
list<<"str1"<<"str2";
///...
qDebug()<<&list;
qDebug()<<&list.at(0);
return list;
}
输出结果:
0x13df5e28
0x13df5e28
0x13df5e28
0x13d95fa0
0x28c79c
0x13d900c0
0x28c79c
0x13d900c0
1. 网上都说是copyOnWrite函数体内&list地址与主函数中&list地址是一样的,结果却是不一致的,但元素地址是一致的,难道错了?理论上,两个list自身的地址应该是不一样的,为什么会结果一样呢?难道是windows销毁前一个list后,凑巧又给后一个list重新分配了一模一样的地址?这与QList使用隐式共享有关系吗?不明白。补充,好像明白了:是因为返回值又产生一个新的隐式共享,对这个list的引用值增加1,既然是赋值,那么导致函数外面那个新的list也使用这个隐式共享,相当于返回值list充当了中介,然后立即减少它的引用值,这样函数内的list始终没有机会被销毁,导致最后的list使用了前面同一个list,此时其引用数为1。
2. 使用[]运算,数据结构经过复制,不再隐式共享。(在只读的情况下,使用at()方法要比使用[]运算子效率高,因为省去了数据结构的复制成本)。
参考:http://blog.csdn.net/yestda/article/details/17893221
------------------------------------------------------------------
理论知识:
凡是支持隐式数据共享的 Qt 类都支持类似的操作。用户甚至不需要知道对象其实已经共享。因此,你应该把这样的类当作普通类一样,而不应该依赖于其共享的特色作一些“小动作”。事实上,这些类的行为同普通类一样,只不过添加了可能的共享数据的优点。因此,你大可以使用按值传参,而无须担心数据拷贝带来的性能问题。
注意,前面已经提到过,不要在使用了隐式数据共享的容器上,在有非 const STL 风格的遍历器正在遍历时复制容器。另外还有一点,对于QList或者QVector,我们应该使用at()函数而不是 [] 操作符进行只读访问。原因是 [] 操作符既可以是左值又可以是右值,这让 Qt 容器很难判断到底是左值还是右值,这意味着无法进行隐式数据共享;而at()函数不能作左值,因此可以进行隐式数据共享。另外一点是,对于begin(),end()以及其他一些非 const 遍历器,由于数据可能改变,因此 Qt 会进行深复制。为了避免这一点,要尽可能使用const_iterator、constBegin()和constEnd()。
参考:http://jukezhang.com/2014/11/23/learn-qt-eight/
--------------------------------------------------------------------
总结:到今天我才算明白,什么是引用计数。一定要对某个QString经过赋值过程(=)以后,才会增加引用计数,或者发生copyOnWrite。而不是说天马行空给一个新字符串直接赋值,比如执行一句QString str1="aaaa",这种情况下,即使另一个字符串str2刚巧目前也是"aaaa",也不会对str2产生增加引用计数,而是创造一个新的字符串"aaaa"在内存中,此时str1和str2分别指向不同地址的字符串,尽管其内容相同,但它们的引用计数都是1。更不是当执行QString str1="aaaa"的时候,在当前程序的全部内存里搜索有没有另一个字符串的内容刚好是"aaaa",然后给它增加引用计数,没有的话,才创造一个新的"aaaa"字符串(那样效率会多么低啊,虽然也有点纳闷,但以前我就是这样理解的)。Delphi里也是同理,以前不明白,现在明白了。
附加总结1(关于字符串):在函数内定义一个QString str1,这个str1仅仅代表一个字符串指针而已,虽然str1指针是在stack上分配的,但其真正的字符串内容仍然是存储在heap里的。sizeof(str1)=4也可证明这一点,无论str1是什么值都是这样。同时sizeof(QString)=4,永远都是这样。经测试,Delphi里也完全如此!因为两者都是采用了引用计数的方法嘛!既然引用计数,就不能是当场分配全部的内存空间存储用来存储全部的数据,而只能是现在这个样子。
附加总结2(关于指针):上面第三个例子的list,说明它的地址不是当场在stack或者heap里分配的,而是之前内存里就存在的一个地址。这对我对指针有了新的理解——不是什么指针都是新分配的,要看这个数据类型是不是具有隐式共享的特征,如果是,就要小心,它不一定分配新的内存地址,仅仅是指针地址也不会分配!
最后附上整个项目文件:http://files.cnblogs.com/files/findumars/testmem.rar
--------------------------------------------------------------------
最后就是好奇,在编译器不是自己做的情况下(Delphi的字符串引用计数是在编译器级实现的),如何实现隐式共享的。想了想,应该是重载operator =,全都返回一个引用,查了一下果然如此(除了QCache):
http://doc.qt.io/qt-4.8/implicit-sharing.html
为了增加对引用的理解,做了一个小例子:
void MainWindow::on_pushButton_10_clicked()
{
int a=999;
int& b = a;
qDebug() << &a <<", "<< &b;
}
输出结果:
0x28d350 , 0x28d350
两个变量的地址值果然完全是一致的。这里特别强调,引用并不产生对象的副本,仅仅是对象的同义词。另外提一句,在引用当作参数传给函数的时候,引用的本质就是间接寻址。
为了进一步加深大家对指针和引用的区别,下面我从编译的角度来阐述它们之间的区别:
程序在编译时分别将指针和引用添加到符号表上,符号表上记录的是变量名及变量所对应地址。指针变量在符号表上对应的地址值为指针变量的地址值,而引用在符号表上对应的地址值为引用对象的地址值。符号表生成后就不会再改,因此指针可以改变其指向的对象(指针变量中的值可以改),而引用对象则不能修改。
参考:http://www.cnblogs.com/yanlingyin/archive/2011/12/07/2278961.html
--------------------------------------------------------------------
最后再来一个例子,两者使用内存的区别十分明显:
void MainWindow::on_pushButton_2_clicked()
{
QStringList list;
for (int i=0; i<5000000; i++) {
QString str = "aaaa";
list << str;
}
QMessageBox::question(NULL, "Test", "finish", QMessageBox::Yes);
}
和
void MainWindow::on_pushButton_3_clicked()
{
QStringList list;
QString str = "aaaa";
for (int i=0; i<5000000; i++) {
list << str;
}
QMessageBox::question(NULL, "Test", "finish", QMessageBox::Yes);
}
两段代码会使用完全不同的内存大小。因为第一个程序在内存里产生了5百万个"aaaa"字符串,使用内存多达220M,而第二个程序在内存中只有一个字符串"aaaa",但这个字符串的引用计数在不断地变化直至500万,运行后发现只使用了25M左右的内存,这就是引用计数的魅力。