1, Add an event to the event loop
[static] void QCoreApplication::postEvent(QObject *receiver, QEvent *event, int priority = Qt::NormalEventPriority)
1.postEvent is to add an event event to the event queue of the thread to which the receiver object belongs and execute it in the next event cycle. It is asynchronous execution. After completion, postEvent() returns immediately. After addition, the ownership of the event belongs to the thread event queue. Therefore, event events should be allocated on the heap. Priority is the priority of events. Events with high priority will be executed first.
[static] bool QCoreApplication::sendEvent(QObject *receiver, QEvent *event)
2.sendEvent() is a synchronous event dispatch. It does not enter the event queue, but directly enters the dispatch phase. It is executed in a thread. In other words, the thread where the sendEvent() interface is called and the thread represented by receiver - > thread() must be the same thread. After the event is sent, sendEvent is not responsible for deleting the event pointer. Therefore, when used, the event must be created on the stack. Here is an example I used in my project:
//QTreeWidget page up void setTreeWidgetUpPage(QTreeView *tree) { if(tree == nullptr) return; QKeyEvent pageUpEvent(QEvent::KeyPress,Qt::Key_PageUp,Qt::NoModifier); Q_ASSERT(QThread::currentThread() == tree->thread());//It must be satisfied, otherwise you need to use postEvent QApplication::sendEvent(tree,&pageUpEvent); }
Take another example to experience the use environment of these two interfaces
//E:\Qt\qt-everywhere-src-6.2.1\qtbase\src\corelib\kernel\qcoreapplication.cpp:2024 void QCoreApplicationPrivate::quit() { Q_Q(QCoreApplication); if (QThread::currentThread() == mainThread()) { QEvent quitEvent(QEvent::Quit);//sendEvent is used when the main thread calls quit QCoreApplication::sendEvent(q, &quitEvent); } else {//When other worker threads call quit, they need to execute asynchronously. Use postEvent. Note that new is used to allocate memory on the heap at this time QCoreApplication::postEvent(q, new QEvent(QEvent::Quit)); } }
2, Event loop in QThread
1. Borrow an example from another article Qtthread of Qt source code analysis As mentioned in the article, the virtual function [virtual protected] void QThread::run() is called in the real thread entry QThreadPrivate::start(), so let's start here.
void QThread::run() { (void) exec(); }
2. In addition to using void QObject::moveToThread(QThread *targetThread) to implement multithreading described in the previous article, it can also be implemented by inheriting QThread class and rewriting QThread::run(). The code executed in run() runs in a new thread. There is no problem in implementation, but this usage is not recommended. The default implementation of run() function is very simple, The exec() function is called to open the thread's event loop.
//E:\Qt\qt-everywhere-src-6.2.1\qtbase\src\corelib\thread\qthread.cpp:543 int QThread::exec() { Q_D(QThread); QMutexLocker locker(&d->mutex); d->data->quitNow = false; if (d->exited) { d->exited = false; return d->returnCode; } locker.unlock(); QEventLoop eventLoop; int returnCode = eventLoop.exec();//Open thread event loop locker.relock(); d->exited = false; d->returnCode = -1; return returnCode; }
3. The event loop will run until the exit() interface is called. It can be seen that the QEventLoop class is also used to open the event loop of the thread. QEventLoop is a derived class of QObject and also has thread affinity. According to the principle of which thread is created, the eventLoop instance also lives in the new thread.
//E:\Qt\qt-everywhere-src-6.2.1\qtbase\src\corelib\kernel\qeventloop.cpp:181 int QEventLoop::exec(ProcessEventsFlags flags) { Q_D(QEventLoop); auto threadData = d->threadData.loadRelaxed();//The QThreadData thread data here is constructed in the constructor of QThreadPrivate, //All qobjects and their derived classes belonging to this thread hold this pointer, which reflects the thread affinity of QObject. //we need to protect from race condition with QThread::exit QMutexLocker locker(&static_cast<QThreadPrivate *>(QObjectPrivate::get(threadData->thread.loadAcquire()))->mutex); if (threadData->quitNow) return -1; if (d->inExec) { qWarning("QEventLoop::exec: instance %p has already called exec()", this); return -1; } struct LoopReference { QEventLoopPrivate *d; QMutexLocker<QMutex> &locker; bool exceptionCaught; LoopReference(QEventLoopPrivate *d, QMutexLocker<QMutex> &locker) : d(d), locker(locker), exceptionCaught(true) { d->inExec = true; d->exit.storeRelease(false); auto threadData = d->threadData.loadRelaxed(); ++threadData->loopLevel;//Open the event loop and add 1 to the number of nested layers threadData->eventLoops.push(d->q_func());//This event loop is added to the event loop queue locker.unlock(); } ~LoopReference() { if (exceptionCaught) { qWarning("Qt has caught an exception thrown from an event handler. Throwing\n" "exceptions from an event handler is not supported in Qt.\n" "You must not let any exception whatsoever propagate through Qt code."); } locker.relock(); auto threadData = d->threadData.loadRelaxed(); QEventLoop *eventLoop = threadData->eventLoops.pop();//Exit the queue after the event cycle exits Q_ASSERT_X(eventLoop == d->q_func(), "QEventLoop::exec()", "internal error"); Q_UNUSED(eventLoop); // --release warning d->inExec = false; --threadData->loopLevel;//Number of nesting layers minus one } }; LoopReference ref(d, locker);//Handle queue in and queue out of event loops in constructors and destructors // remove posted quit events when entering a new event loop QCoreApplication *app = QCoreApplication::instance(); if (app && app->thread() == thread()) QCoreApplication::removePostedEvents(app, QEvent::Quit); #ifdef Q_OS_WASM // Partial support for nested event loops: Make the runtime throw a JavaSrcript // exception, which returns control to the browser while preserving the C++ stack. // Event processing then continues as normal. The sleep call below never returns. // QTBUG-70185 if (threadData->loopLevel > 1) emscripten_sleep(1); #endif while (!d->exit.loadAcquire())//As long as you do not exit, you will always wait and process the events in the event queue processEvents(flags | WaitForMoreEvents | EventLoopExec); ref.exceptionCaught = false; return d->returnCode.loadRelaxed(); }
- enum ProcessEventsFlags
- QEventLoop::AllEvents: process all events
- QEventLoop::ExcludeUserInputEvents: do not process user input events. Events will not be discarded and will be processed without this flag
- QEventLoop::ExcludeSocketNotifiers: do not process network events
- QEventLoop::WaitForMoreEvents: when the event queue is empty, continue to wait, and the thread does not sleep
bool QEventLoop::processEvents(ProcessEventsFlags flags) { Q_D(QEventLoop); auto threadData = d->threadData.loadRelaxed(); if (!threadData->hasEventDispatcher()) return false; return threadData->eventDispatcher.loadRelaxed()->processEvents(flags); }
Recall that there are several important members in QThreadData, which store the thread's event loop queue, event queue, thread pointer, thread ID and event distribution handle. Please post the code
class QThreadData { public: QThreadData(int initialRefCount = 1); ~QThreadData(); ... public: int loopLevel;//Number of event loop nesting levels int scopeLevel; QStack<QEventLoop *> eventLoops;//Nested event loop stack QPostEventList postEventList;//Thread event queue QAtomicPointer<QThread> thread;//Thread pointer QAtomicPointer<void> threadId;//Thread ID QAtomicPointer<QAbstractEventDispatcher> eventDispatcher;//Event distribution list QList<void *> tls; FlaggedDebugSignatures flaggedSignatures; ... };
QEventLoop::processEvents() further calls the interface of the QAbstractEventDispatcher class. This class is a virtual base class that defines the specific interface for event distribution. There are derivative classes that cannot be implemented on different platforms, so as to achieve cross platform purposes. Qeventdispatcher UNIX under Linux and qeventdispatcher Win32 under window. Let's take a look at the specific implementation under win, Let's look at the constructor first
QEventDispatcherWin32::QEventDispatcherWin32(QEventDispatcherWin32Private &dd, QObject *parent) : QAbstractEventDispatcher(dd, parent) { Q_D(QEventDispatcherWin32); d->internalHwnd = qt_create_internal_window(this); }
There is a very important point here. For window programming under win32, registering message callback functions can't get around, and Qt is no exception. In Qt, an invisible window is also created for each thread, and then the message callback function is registered for the window to take over the message loop of windows and translate the messages in Windows format into Qt's own message format, Enter Qt event system. D - > internalhwnd = Qt in constructor_ create_ internal_ window(this); Is the concrete realization,
Look at the code:
static HWND qt_create_internal_window(const QEventDispatcherWin32 *eventDispatcher) { QWindowsMessageWindowClassContext *ctx = qWindowsMessageWindowClassContext(); if (!ctx->atom) return 0; HWND wnd = CreateWindow(ctx->className, // classname ctx->className, // window name 0, // style 0, 0, 0, 0, // The geometry size is 0. This window is invisible HWND_MESSAGE, // parent 0, // menu handle GetModuleHandle(0), // application 0); // windows creation data. if (!wnd) { qErrnoWarning("CreateWindow() for QEventDispatcherWin32 internal window failed"); return 0; } #ifdef GWLP_USERDATA SetWindowLongPtr(wnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(eventDispatcher)); #else SetWindowLong(wnd, GWL_USERDATA, reinterpret_cast<LONG>(eventDispatcher)); #endif return wnd; }
Note that QWindowsMessageWindowClassContext ctx = qWindowsMessageWindowClassContext(); What is obtained here is a global static variable, which uses a macro Q defined in Qt_ GLOBAL_ Static, used to define a thread safe global static variable, Q_GLOBAL_STATIC(QWindowsMessageWindowClassContext, qWindowsMessageWindowClassContext), qWindowsMessageWindowClassContext is actually an object, but it is overloaded with (), - >, and other operators. In behavior, it is expressed as a pointer to the object. Let's take a look at the constructor of this class.
QWindowsMessageWindowClassContext::QWindowsMessageWindowClassContext() : atom(0), className(0) { // make sure that multiple Qt's can coexist in the same process const QString qClassName = QStringLiteral("QEventDispatcherWin32_Internal_Widget") + QString::number(quintptr(qt_internal_proc)); className = new wchar_t[qClassName.size() + 1]; qClassName.toWCharArray(className); className[qClassName.size()] = 0; WNDCLASS wc; wc.style = 0; wc.lpfnWndProc = qt_internal_proc;//Here's the point. This is the message callback function of the above invisible window, which is a connection point between the windows message system and Qt wc.cbClsExtra = 0; //It will not be expanded here for the time being and will be described in detail later wc.cbWndExtra = 0; wc.hInstance = GetModuleHandle(0); wc.hIcon = 0; wc.hCursor = 0; wc.hbrBackground = 0; wc.lpszMenuName = NULL; wc.lpszClassName = className; atom = RegisterClass(&wc); if (!atom) { qErrnoWarning("%ls RegisterClass() failed", qUtf16Printable(qClassName)); delete [] className; className = 0; } }
In win32 window programming, when processing message loops, they are generally handled as follows:
MSG msg; while(!isExit()){ if(PeekMessage(&msg, 0, 0, 0, PM_REMOVE)){ TranslateMessage(&msg); DispatchMessage(&msg); } }
In a while loop, keep getting messages from the windows message loop through PeekMessage, and then translate and distribute the messages. Finally, we registered the callback function qt_internal_proc() further processes messages. The processing of getting and distributing messages in Qt is in the processEvents() function. Let's go back here and have a look
bool QEventDispatcherWin32::processEvents(QEventLoop::ProcessEventsFlags flags) { Q_D(QEventDispatcherWin32); // We don't know _when_ the interrupt occurred so we have to honor it. const bool wasInterrupted = d->interrupt.fetchAndStoreRelaxed(false); emit awake(); // To prevent livelocks, send posted events once per iteration. // QCoreApplication::sendPostedEvents() takes care about recursions. sendPostedEvents(); if (wasInterrupted) return false; auto threadData = d->threadData.loadRelaxed(); bool canWait; bool retVal = false; do { QVarLengthArray<MSG> processedTimers; while (!d->interrupt.loadRelaxed()) { MSG msg; if (!(flags & QEventLoop::ExcludeUserInputEvents) && !d->queuedUserInputEvents.isEmpty()) { // process queued user input events msg = d->queuedUserInputEvents.takeFirst();//Retrieve user input message events } else if (!(flags & QEventLoop::ExcludeSocketNotifiers) && !d->queuedSocketEvents.isEmpty()) { // process queued socket events msg = d->queuedSocketEvents.takeFirst();//Fetch network socket message event } else if (PeekMessage(&msg, 0, 0, 0, PM_REMOVE)) {//PeekMessage is a win32 api used to retrieve messages from windows system message queues if (flags.testFlag(QEventLoop::ExcludeUserInputEvents)//Get message && isUserInputMessage(msg.message)) { // queue user input events for later processing d->queuedUserInputEvents.append(msg); continue; } if ((flags & QEventLoop::ExcludeSocketNotifiers) && (msg.message == WM_QT_SOCKETNOTIFIER && msg.hwnd == d->internalHwnd)) { // queue socket events for later processing d->queuedSocketEvents.append(msg); continue; } } else if (MsgWaitForMultipleObjectsEx(0, NULL, 0, QS_ALLINPUT, MWMO_ALERTABLE) == WAIT_OBJECT_0) { // a new message has arrived, process it continue; } else { // nothing to do, so break break; } if (d->internalHwnd == msg.hwnd && msg.message == WM_QT_SENDPOSTEDEVENTS) { d->startPostedEventsTimer(); // Set result to 'true' because the message was sent by wakeUp(). retVal = true; continue; } if (msg.message == WM_TIMER) {//Processing timer events // Skip timer event intended for use inside foreign loop. if (d->internalHwnd == msg.hwnd && msg.wParam == d->sendPostedEventsTimerId) continue; // avoid live-lock by keeping track of the timers we've already sent bool found = false; for (int i = 0; !found && i < processedTimers.count(); ++i) { const MSG processed = processedTimers.constData()[i]; found = (processed.wParam == msg.wParam && processed.hwnd == msg.hwnd && processed.lParam == msg.lParam); } if (found) continue; processedTimers.append(msg); } else if (msg.message == WM_QUIT) { if (QCoreApplication::instance()) QCoreApplication::instance()->quit(); return false; } //The filtering of events natively supported by each platform will return true if processed if (!filterNativeEvent(QByteArrayLiteral("windows_generic_MSG"), &msg, 0)) { TranslateMessage(&msg); DispatchMessage(&msg);//The win32 api enters the message distribution phase, and the event enters the message cycle of the windows system } retVal = true; } // wait for message canWait = (!retVal && !d->interrupt.loadRelaxed() && flags.testFlag(QEventLoop::WaitForMoreEvents) && threadData->canWaitLocked()); if (canWait) { emit aboutToBlock(); MsgWaitForMultipleObjectsEx(0, NULL, INFINITE, QS_ALLINPUT, MWMO_ALERTABLE | MWMO_INPUTAVAILABLE); emit awake(); } } while (canWait); return retVal; }
After the message is distributed, the previously registered message callback function Qt will be called through the windows message system_ internal_ Proc (), where windows system messages are translated into Qt messages, and the familiar events are programmed.
//E:\Qt\qt-everywhere-src-6.2.1\qtbase\src\corelib\kernel\qeventdispatcher_win.cpp:241 LRESULT QT_WIN_CALLBACK qt_internal_proc(HWND hwnd, UINT message, WPARAM wp, LPARAM lp) { if (message == WM_NCCREATE) return true; MSG msg; msg.hwnd = hwnd; msg.message = message; msg.wParam = wp; msg.lParam = lp; QAbstractEventDispatcher* dispatcher = QAbstractEventDispatcher::instance(); qintptr result; if (!dispatcher) { if (message == WM_TIMER) KillTimer(hwnd, wp); return 0; } if (dispatcher->filterNativeEvent(QByteArrayLiteral("windows_dispatcher_MSG"), &msg, &result)) return result; #ifdef GWLP_USERDATA auto q = reinterpret_cast<QEventDispatcherWin32 *>(GetWindowLongPtr(hwnd, GWLP_USERDATA)); #else auto q = reinterpret_cast<QEventDispatcherWin32 *>(GetWindowLong(hwnd, GWL_USERDATA)); #endif QEventDispatcherWin32Private *d = nullptr; if (q != nullptr) d = q->d_func(); switch (message) { case WM_QT_SOCKETNOTIFIER: {//Network socket events // socket notifier message int type = -1; switch (WSAGETSELECTEVENT(lp)) { case FD_READ: case FD_ACCEPT: type = 0; break; case FD_WRITE: case FD_CONNECT: type = 1; break; case FD_OOB: type = 2; break; case FD_CLOSE: type = 3; break; } if (type >= 0) { Q_ASSERT(d != nullptr); QSNDict *sn_vec[4] = { &d->sn_read, &d->sn_write, &d->sn_except, &d->sn_read }; QSNDict *dict = sn_vec[type]; QSockNot *sn = dict ? dict->value(wp) : 0; if (sn == nullptr) { d->postActivateSocketNotifiers(); } else { Q_ASSERT(d->active_fd.contains(sn->fd)); QSockFd &sd = d->active_fd[sn->fd]; if (sd.selected) { Q_ASSERT(sd.mask == 0); d->doWsaAsyncSelect(sn->fd, 0); sd.selected = false; } d->postActivateSocketNotifiers(); // Ignore the message if a notification with the same type was // received previously. Suppressed message is definitely spurious. const long eventCode = WSAGETSELECTEVENT(lp); if ((sd.mask & eventCode) != eventCode) { sd.mask |= eventCode; QEvent event(type < 3 ? QEvent::SockAct : QEvent::SockClose);//socket related Qt events QCoreApplication::sendEvent(sn->obj, &event); } } } return 0; } case WM_QT_ACTIVATENOTIFIERS: { Q_ASSERT(d != nullptr); // Postpone activation if we have unhandled socket notifier messages // in the queue. WM_QT_ACTIVATENOTIFIERS will be posted again as a result of // event processing. MSG msg; if (!PeekMessage(&msg, d->internalHwnd, WM_QT_SOCKETNOTIFIER, WM_QT_SOCKETNOTIFIER, PM_NOREMOVE) && d->queuedSocketEvents.isEmpty()) { // register all socket notifiers for (QSFDict::iterator it = d->active_fd.begin(), end = d->active_fd.end(); it != end; ++it) { QSockFd &sd = it.value(); if (!sd.selected) { d->doWsaAsyncSelect(it.key(), sd.event); // allow any event to be accepted sd.mask = 0; sd.selected = true; } } } d->activateNotifiersPosted = false; return 0; } case WM_TIMER://timer event Q_ASSERT(d != nullptr); if (wp == d->sendPostedEventsTimerId) q->sendPostedEvents(); else d->sendTimerEvent(wp); return 0; case WM_QT_SENDPOSTEDEVENTS://post events are handled here Q_ASSERT(d != nullptr); // We send posted events manually, if the window procedure was invoked // by the foreign event loop (e.g. from the native modal dialog). // Skip sending, if the message queue is not empty. // sendPostedEventsTimer will deliver posted events later. static const UINT mask = QS_ALLEVENTS; if (HIWORD(GetQueueStatus(mask)) == 0) q->sendPostedEvents();//Send the events in the event queue uniformly else d->startPostedEventsTimer(); return 0; } // switch (message) return DefWindowProc(hwnd, message, wp, lp); }
Void qeventdispatcher Win32:: sendpostedevents(), GUI related events are handled differently from other events here. QEvent::MetaCall such events related to slot function callback are handled through this interface, while window related events will enter void QWindowsGuiEventDispatcher::sendPostedEvents() in another overload function, which will be described in the event loop of the main thread, Here's the code:
//E:\Qt\qt-everywhere-src-6.2.1\qtbase\src\corelib\kernel\qeventdispatcher_win.cpp:892 void QEventDispatcherWin32::sendPostedEvents() { Q_D(QEventDispatcherWin32); if (d->sendPostedEventsTimerId != 0) KillTimer(d->internalHwnd, d->sendPostedEventsTimerId); d->sendPostedEventsTimerId = 0; // Allow posting WM_QT_SENDPOSTEDEVENTS message. d->wakeUps.storeRelaxed(0); QCoreApplicationPrivate::sendPostedEvents(0, 0, d->threadData.loadRelaxed()); }
You can see that the thread data pointer of this thread is sent out as a parameter here. The first parameter is receiver=0,event_type=0
//E:\Qt\qt-everywhere-src-6.2.1\qtbase\src\corelib\kernel\qcoreapplication.cpp:1838 void QCoreApplicationPrivate::sendPostedEvents(QObject *receiver, int event_type, QThreadData *data) { if (event_type == -1) { // we were called by an obsolete event dispatcher. event_type = 0; } if (receiver && receiver->d_func()->threadData != data) { qWarning("QCoreApplication::sendPostedEvents: Cannot send " "posted events for objects in another thread"); return; } ++data->postEventList.recursion; auto locker = qt_unique_lock(data->postEventList.mutex); // by default, we assume that the event dispatcher can go to sleep after // processing all events. if any new events are posted while we send // events, canWait will be set to false. data->canWait = (data->postEventList.size() == 0); if (data->postEventList.size() == 0 || (receiver && !receiver->d_func()->postedEvents)) { --data->postEventList.recursion; return; } data->canWait = true; // okay. here is the tricky loop. be careful about optimizing // this, it looks the way it does for good reasons. int startOffset = data->postEventList.startOffset; int &i = (!event_type && !receiver) ? data->postEventList.startOffset : startOffset; data->postEventList.insertionOffset = data->postEventList.size(); // Exception-safe cleaning up without the need for a try/catch block struct CleanUp { QObject *receiver; int event_type; QThreadData *data; bool exceptionCaught; inline CleanUp(QObject *receiver, int event_type, QThreadData *data) : receiver(receiver), event_type(event_type), data(data), exceptionCaught(true) {} inline ~CleanUp() { if (exceptionCaught) { // since we were interrupted, we need another pass to make sure we clean everything up data->canWait = false; } --data->postEventList.recursion; if (!data->postEventList.recursion && !data->canWait && data->hasEventDispatcher()) data->eventDispatcher.loadRelaxed()->wakeUp(); // clear the global list, i.e. remove everything that was // delivered. if (!event_type && !receiver && data->postEventList.startOffset >= 0) { const QPostEventList::iterator it = data->postEventList.begin(); data->postEventList.erase(it, it + data->postEventList.startOffset); data->postEventList.insertionOffset -= data->postEventList.startOffset; Q_ASSERT(data->postEventList.insertionOffset >= 0); data->postEventList.startOffset = 0; } } }; CleanUp cleanup(receiver, event_type, data); while (i < data->postEventList.size()) { // avoid live-lock if (i >= data->postEventList.insertionOffset) break; const QPostEvent &pe = data->postEventList.at(i); ++i; if (!pe.event) continue; if ((receiver && receiver != pe.receiver) || (event_type && event_type != pe.event->type())) { data->canWait = false; continue; } if (pe.event->type() == QEvent::DeferredDelete) { // DeferredDelete events are sent either // 1) when the event loop that posted the event has returned; or // 2) if explicitly requested (with QEvent::DeferredDelete) for // events posted by the current event loop; or // 3) if the event was posted before the outermost event loop. int eventLevel = static_cast<QDeferredDeleteEvent *>(pe.event)->loopLevel(); int loopLevel = data->loopLevel + data->scopeLevel; const bool allowDeferredDelete = (eventLevel > loopLevel || (!eventLevel && loopLevel > 0) || (event_type == QEvent::DeferredDelete && eventLevel == loopLevel)); if (!allowDeferredDelete) { // cannot send deferred delete if (!event_type && !receiver) { // we must copy it first; we want to re-post the event // with the event pointer intact, but we can't delay // nulling the event ptr until after re-posting, as // addEvent may invalidate pe. QPostEvent pe_copy = pe; // null out the event so if sendPostedEvents recurses, it // will ignore this one, as it's been re-posted. const_cast<QPostEvent &>(pe).event = nullptr; // re-post the copied event so it isn't lost data->postEventList.addEvent(pe_copy); } continue; } } // first, we diddle the event so that we can deliver // it, and that no one will try to touch it later. pe.event->m_posted = false; QEvent *e = pe.event; QObject * r = pe.receiver; --r->d_func()->postedEvents; Q_ASSERT(r->d_func()->postedEvents >= 0); // next, update the data structure so that we're ready // for the next event. const_cast<QPostEvent &>(pe).event = nullptr; locker.unlock(); const auto relocker = qScopeGuard([&locker] { locker.lock(); }); QScopedPointer<QEvent> event_deleter(e); // will delete the event (with the mutex unlocked) // after all that work, it's time to deliver the event. QCoreApplication::sendEvent(r, e); // careful when adding anything below this point - the // sendEvent() call might invalidate any invariants this // function depends on. } cleanup.exceptionCaught = false; }
CleanUp, an internal class, is used to empty the event queue after sending out the events in the queue through sendEvent. As we said before, the event queue holds the ownership of the events allocated on the heap and is responsible for its memory release, qscopedpointer event_ Delete (E) by transferring the ownership of the event pointer to the smart pointer event_ On the delete, it is automatically released after exceeding the function of the smart pointer. Next, we will see the sendEvent we are familiar with. It will not be expanded here. We will open a single chapter later.