Qt 源码阅读随笔

  • QFile 源码最终落入 qfilesystemengine_win.cpp, qfilesystemengine_unix.cpp 等平台相关的源文件中。

  • 在使用样式表时,QWidget 系列组件将由 qstylesheetstyle 类负责绘制。至于具体的绘制操作, 则是由 QRenderRule 封装并暴露相关接口。

  • 一但 Widget 上设置了样式表或设置了应用程序级的样式表, 那么其 style() 将始终变为 qstylesheetstyle, 其先前通过 setStyle 设置的自定义 style 将被 qstylesheetstyle 代理。

  • windows 下, 所有 Qt 窗口的窗口类都将落入 QWindowsContext::registerWindowClass(const QWindow *w) 中 进行注册,

    同时, 其窗口过程被注册为 qWindowsWndProc。之后,其窗口过程的主要处理操作由 QWindowsContext::windowsProc 进行。

    其中, QAbstractNativeEventFilter 的触发调用也是在 QWindowsContext::windowsProc 中进行的。

    需要注意的是, QAbstractNativeEventFilter 仅在 !isInputMessage(msg.message) 返回 false 时触发调用。如果没有触发或没有处理,则触发 QWindow::nativeEventQWidget::nativeEvent。 (待验证)

  • QTimer::singleShot 通过在目标对象的线程上下文中创建一个 Timer(QSingleShotTimer) 对象来实现工作于目标线程上的计时器。

  • QTimer 内部也是通过 QObject::startTimer 方法实现的。而在 QObject::startTimer 方法中,其向当前对象所属线程的事件分发器注册了定时器:

    int timerId = thisThreadData->eventDispatcher.loadRelaxed()->registerTimer(interval, timerType, this);

  • QThread.start 在内部基于平台 API 创建线程并传递 QThreadPrivate::start 作为线程的执行例程。

    void QThread::start(Priority priority)
    {
    	......
        int code = pthread_create(&threadId, &attr, QThreadPrivate::start, this);
    	......
    }
    
    void *QThreadPrivate::start(void *arg)
    {
    	......
    	pthread_cleanup_push(QThreadPrivate::finish, arg);   // 创建了一个局部对象以在退出 start 方法时执行 QThreadPrivate::finish
    	......
    }
    
  • QObject::deleteLater 依赖对象所属线程的事件循环,这也意味着如果对象所属线程没有事件循环时其将得不到释放。并且绝不可以在线程对象自身的事件循环中删除自身 (避免 t->moveToThread(t) 这种写法),这必然会导致访问错误。

    void QObject::deleteLater()
    {
      ...
      QCoreApplication::postEvent(this, new QDeferredDeleteEvent());
      ...
    }
    
    
      void QCoreApplication::postEvent(QObject *receiver, QEvent *event, int priority)
        {
          ...
          data->postEventList.addEvent(QPostEvent(receiver, event, priority));  // 添加事件至事件队列
          ...
          /* 启动事件分发 */
          QAbstractEventDispatcher* dispatcher = data->eventDispatcher.loadAcquire();
              if (dispatcher)
                  dispatcher->wakeUp();
          ...
        }
    
  • QMetaObject::invokeMethod(QObject*context, Functor *function, FunctorReturnType *ret) 将传入的函数对象/函数指针/成员函数指针通过一个 QFunctorSlotObject 对象来包装,该对象采用了元模板编程技术来在运行时解析出传入的函数类型信息,并实例化相应的模板类和调用代码。

  • 通过 qobject_p.h 中的 qt_register_signal_spy_callbacks 函数,我们可以跟踪槽函数的触发调用。详情参见 qsignaldumper.cpp 中的 QSignalDumper::startDumpqobject.cpp 中的 doActivate

  • QThread 内部在发出 finished 信号之后调用 QCoreApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete) 以实现其线程内对象的延迟删除。

    void QThreadPrivate::finish(void *arg)
    {
    ...
    emit thr->finished(QThread::QPrivateSignal());
    QCoreApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete);
    ...
    }
    void QCoreApplicationPrivate::sendPostedEvents(QObject *receiver, int event_type, QThreadData *data)
    {
    ...
    // 遍历当前线程的事件队列
    while (i < data->postEventList.size()) {
    ...
    ...
    }
    ...
    }
    

    思考如下代码:

    QString gStr;
    QString getStr() { return gStr; }  // t1线程访问
    void setStr(const QString& s) { gStr = s; }  // t2线程访问
    
    /* QString 拷贝构造函数 */
    inline QString::QString(const QString &other) noexcept : d(other.d)
    { Q_ASSERT(&other != this); d->ref.ref(); }
    
    /* QString 移动赋值运算符 */
    inline QString &operator=(QString &&other) noexcept
    { qSwap(d, other.d); return *this; }
    inline void swap(QString &other) noexcept { qSwap(d, other.d); }
    

    我们可能会认为 Qt 的隐式共享能保证多线程下安全的分离对象,但是事实并非如此, 思考如下执行场景:

    // 现有线程 t1 和 t2, 它们的执行流如下:
    t1 inline QString::QString(const QString &other) noexcept : d(other.d)
    t2 inline QString &operator=(QString &&other) noexcept
    t2 { qSwap(d, other.d); return *this; }
    t2 inline void swap(QString &other) noexcept { qSwap(d, other.d); }
    t2 inline QString::~QString() { if (!d->ref.deref()) Data::deallocate(d); }   // 旧值被释放, 而先前 t1 还未完成引用计数的增加
    t1 { Q_ASSERT(&other != this); d->ref.ref(); }    // 崩溃, d 已被释放
    

    并且考虑到内存可见性和 Qt 的引用计数实现无法保证线程安全, 因此该问题会比预期的更容易出现:

    class RefCount
    {
    public:
        inline bool ref() noexcept {
            int count = atomic.loadRelaxed();
    #if !defined(QT_NO_UNSHARABLE_CONTAINERS)
            if (count == 0) // !isSharable
                return false;
    #endif
            if (count != -1) // !isStatic
                atomic.ref();
            return true;
        }
        inline bool deref() noexcept {
            int count = atomic.loadRelaxed();
    #if !defined(QT_NO_UNSHARABLE_CONTAINERS)
            if (count == 0) // !isSharable
                return false;
    #endif
            if (count == -1) // isStatic
                return true;
            return atomic.deref();
        }
        QBasicAtomicInt atomic;
    };
    

    总的来说, Qt 的隐式共享只能够保证多线程下安全的创建副本, 但是如果同时有其它线程在修改被共享的副本, 那么将会出现竞态问题。

    额外阅读:

    https://doc.qt.io/qt-5/threads-modules.html#threads-and-implicitly-shared-classes

    https://stackoverflow.com/questions/49072060/qt-is-it-thread-safe-to-copy-an-instance-of-an-implicitly-shared-classes

    http://www.folding-hyperspace.com/real-time-programming-tips/tip-15-implicit-sharing-and.html

  • QThread::wait 通过阻塞调用线程并等待线程执行完毕时释放的等待条件 (Windows 下等待线程句柄, Linux 下等待条件变量) 来实现:

    bool QThread::wait(QDeadlineTimer deadline)
    {
        ...
        while (d->running) {
            if (!d->thread_done.wait(locker.mutex(), deadline))
                return false;
        }
        ...
    }
    
    void QThreadPrivate::finish(void *arg)
    {
            ...
            emit thr->finished(QThread::QPrivateSignal());
            QCoreApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete);
            ...
            d->thread_done.wakeAll();
            ...
    }
    
  • QObjectPrivate::addConnectionQObjectPrivate::ConnectionData::removeConnection 实现了信号槽的连接记录和移除,并且从源码中可以看到,发送者和接收者共用一个 Connection 对象。

  • 同一个线程上下文中的对象共享一份 QThreadData 实例?QThreadData 存储的信息有:

    int loopLevel;
    int scopeLevel;
    QStack<QEventLoop *> eventLoops;
    QPostEventList postEventList;
    QAtomicPointer<QThread> thread;
    QAtomicPointer<void> threadId;
    QAtomicPointer<QAbstractEventDispatcher> eventDispatcher;
    QVector<void *> tls;
    FlaggedDebugSignatures flaggedSignatures;
    bool quitNow;
    bool canWait;
    bool isAdopted;
    bool requiresCoreApplication;
    
  • 为了实现二进制兼容性和隐藏实现细节,Qt 将类的成员属性和可能会产生变化的成员方法封装至专门的 Private 对象中。
    每个 Qt 导出类在其构造函数中都会确保创建一个对应的 Private 对象。
    并且一些父类会提供一个如下格式的构造函数,以供子类调用,用以实现多态:

    protected:
        QObject(QObjectPrivate &dd, QObject *parent = nullptr);
    

    同时,似乎所有 Qt 控件类的 Private 类都包含有一个 init 方法,控件类会确保在其构造函数中调用 Private 类的 init 方法以确保初始化样式信息。

    其中 QWidgetPrivate::init 执行的内容最为重要,查看其源码可了解一个控件类在创建后所会执行的操作。

    由于 Private 对象的设计,我们很难扩展现有的 Qt 类(如果需要访问其私有成员的话)。

  • QString::lastIndexOf 最终落入 qLastIndexOf 模板函数, 在该函数内 Qt 反向查找目标字符串。

  • QThread::moveToThread 源码阅读:

    void QObject::moveToThread(QThread *targetThread)
    {
    	......
    	// 虽然要求对象的 parent 为空,但其没有在 parent 上做更多的文章
        if (d->parent != nullptr) 
            return;
        .....
        // gui 组件只允许在 gui 线程创建,也不允许移动至其它线程
        if (d->isWidget)
            return;
        .....
        /* 获取各线程数据对象(是共享的),执行文档所述的限制判断 */
        QThreadData *currentData = QThreadData::current();
        QThreadData *targetData = targetThread ? QThreadData::get2(targetThread) : nullptr;
        QThreadData *thisThreadData = d->threadData.loadRelaxed();
        .....
        // 对当前对象及其所有子对象发送 ThreadChange 事件
        d->moveToThread_helper();
        .....
        // 按照指针比较顺序对两个 mutex 加锁 
        // (如果当前线程先加锁, 而目标线程恰好等待当前线程, 是否会导致在目标线程上死锁?)
        QOrderedMutexLocker locker(&currentData->postEventList.mutex,
                                   &targetData->postEventList.mutex);
        .....
        d_func()->setThreadData_helper(currentData, targetData);
    }
    
    void QObjectPrivate::setThreadData_helper(QThreadData *currentData, QThreadData *targetData)
    {
    	......
        /* 将发送至当前对象的事件移动至目标线程的事件队列  */
        for (int i = 0; i < currentData->postEventList.size(); ++i) {
            const QPostEvent &pe = currentData->postEventList.at(i);
    	    ......
             targetData->postEventList.addEvent(pe);
             ......
        }
        /* 如果有移动的事件, 则换起目标线程的事件分发 */
        if (eventsMoved > 0 && targetData->hasEventDispatcher()) {
            targetData->canWait = false;
            targetData->eventDispatcher.loadRelaxed()->wakeUp();
        }
        ......
        ConnectionData *cd = connections.loadRelaxed();
        ......
            /* 更新信号槽连接的线程数据指针 */
            if (cd) {
                auto *c = cd->senders;
                while (c) {
                    ......
                        c->receiverThreadData.storeRelaxed(targetData);
    			   ......
                }
            }
        ......
        threadData.storeRelease(targetData);
        /* 递归更新子对象 */
        for (int i = 0; i < children.size(); ++i) {
            QObject *child = children.at(i);
            child->d_func()->setThreadData_helper(currentData, targetData);
        }
    }
    
  • QOrderedMutexLocker 源码阅读:

    /* 源码中未找到对于 std::less<QBasicMutex *> 的特例化, 似乎只是简单的比较一下指针, 然后按顺序加锁 */
    QOrderedMutexLocker(QBasicMutex *m1, QBasicMutex *m2)
        : mtx1((m1 == m2) ? m1 : (std::less<QBasicMutex *>()(m1, m2) ? m1 : m2)),
         mtx2((m1 == m2) ?  nullptr : (std::less<QBasicMutex *>()(m1, m2) ? m2 : m1)),
         locked(false)
    {
        relock();
    }
    
  • QWidget 构造函数源码阅读:

    QWidget::QWidget(QWidget *parent, Qt::WindowFlags f)
        : QObject(*new QWidgetPrivate, nullptr), QPaintDevice()
    {
         d_func()->init(parent, f);
    }
    
    void QWidgetPrivate::init(QWidget *parentWidget, Qt::WindowFlags f)
    {
        ......
        if (Q_UNLIKELY(!qobject_cast<QApplication *>(QCoreApplication::instance())))
            qFatal("QWidget: Cannot create a QWidget without QApplication");
        ......
        // QApplication::allWidgets 返回的就是该静态列表,其在 QWidgetPrivate 下定义
        // QWidgetSet *QWidgetPrivate::allWidgets
        if (allWidgets)
            allWidgets->insert(q);
        ......
        int targetScreen = -1;
        /* QDesktopScreenWidget 从 Qt5.11 开始已不再被使用,不需要关注。
         * 因此此处 targetScreen 通常为 -1,如果为 parentWidget->windowType() == Qt::Desktop 则为 0。
         * 在此之前 QDesktopScreenWidget 是一个隐藏显示的 QWidget, 由 QDesktopWidget 内部创建和维护,
         * 用于存储所关联的 QScreen 的屏幕矩形信息,以辅助计算屏幕编号、矩形、变更等信息。
         */
        if (parentWidget && parentWidget->windowType() == Qt::Desktop) {
            const QDesktopScreenWidget *sw = qobject_cast<const QDesktopScreenWidget *>(parentWidget);
            targetScreen = sw ? sw->screenNumber() : 0;
            parentWidget = nullptr;
        }
        ......
        if (targetScreen >= 0) {
            topData()->initialScreenIndex = targetScreen;
            if (QWindow *window = q->windowHandle())
                window->setScreen(QGuiApplication::screens().value(targetScreen, nullptr));
        }
        ......
        data.fstrut_dirty = true;
        data.winid = 0;
        data.widget_attributes = 0;
        data.window_flags = f;
        data.window_state = 0;
        data.focus_policy = 0;
        data.context_menu_policy = Qt::DefaultContextMenu;
        data.window_modality = Qt::NonModal;
        data.sizehint_forced = 0;
        data.is_closing = 0;
        data.in_show = 0;
        data.in_set_window_state = 0;
        data.in_destructor = false;
        // Widgets with Qt::MSWindowsOwnDC (typically QGLWidget) must have a window handle.
        if (f & Qt::MSWindowsOwnDC) {
            mustHaveWindowHandle = 1;
            q->setAttribute(Qt::WA_NativeWindow);
        }
        q->setAttribute(Qt::WA_QuitOnClose); // might be cleared in adjustQuitOnCloseAttribute()
        adjustQuitOnCloseAttribute();
        q->setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea);
        q->setAttribute(Qt::WA_WState_Hidden);
        //give potential windows a bigger "pre-initial" size; create() will give them a new size later
        data.crect = parentWidget ? QRect(0,0,100,30) : QRect(0,0,640,480);
        focus_next = focus_prev = q;
        if ((f & Qt::WindowType_Mask) == Qt::Desktop)
            q->create();
        else if (parentWidget)
            q->setParent(parentWidget, data.window_flags);
        else {
            adjustFlags(data.window_flags, q);
            resolveLayoutDirection();
            // opaque system background?
            const QBrush &background = q->palette().brush(QPalette::Window);
            setOpaque(q->isWindow() && background.style() != Qt::NoBrush && background.isOpaque());
        }
        data.fnt = QFont(data.fnt, q);
        q->setAttribute(Qt::WA_PendingMoveEvent);
        q->setAttribute(Qt::WA_PendingResizeEvent);
        if (++QWidgetPrivate::instanceCounter > QWidgetPrivate::maxInstances)
            QWidgetPrivate::maxInstances = QWidgetPrivate::instanceCounter;
        if (QApplicationPrivate::testAttribute(Qt::AA_ImmediateWidgetCreation)) // ### fixme: Qt 6: Remove AA_ImmediateWidgetCreation.
            q->create();
        QEvent e(QEvent::Create);
        QCoreApplication::sendEvent(q, &e);
        QCoreApplication::postEvent(q, new QEvent(QEvent::PolishRequest));
        extraPaintEngine = nullptr;
    }
    

--- 2024-‎5-‎7
posted @ 2025-12-21 20:30  邓加领  阅读(0)  评论(0)    收藏  举报