Qt对象树析构链与智能指针协同:零泄漏内存管理架构

一个delete引发的连锁反应------Qt对象树如何保证不泄漏哪怕一个字节?

在16年的Qt开发生涯中,我见过无数次内存泄漏:野指针崩溃、循环引用死锁、子线程对象主线程delete......这些问题看似零散,本质上都指向同一个机制------Qt对象树的析构链路。本文从QObject源码的析构入口出发,逐层剖析delete传播、父子关系维护、线程安全deleteLater,最终给出一个与std::shared_ptr协同工作的零泄漏架构。


一、QObject析构入口:链式反应的起点

1.1 析构函数源码剖析

cpp 复制代码
// qtbase/src/corelib/kernel/qobject.cpp
QObject::~QObject()
{
    Q_D(QObject);
    
    // 第一阶段:从父对象中移除
    if (d->parent) {
        d->setParent_helper(nullptr, nullptr, nullptr);
        // setParent_helper内部会从parent的children列表中删除this
    }
    
    // 第二阶段:遍历子对象列表,依次delete每个子对象
    // 这里是关键------对象树递归析构的核心
    QObjectPrivate::deleteChildren(d);
    
    // 第三阶段:清理事件过滤器
    if (d->eventFilter) {
        // 通知所有安装了此对象作为事件过滤器的目标
        for (int i = 0; i < d->eventFilter->size(); ++i) {
            QObject *obj = d->eventFilter->at(i);
            obj->removeEventFilter(this);
        }
    }
    
    // 第四阶段:从Connection列表中断开所有信号槽
    // 注意:这里只断开this发出的信号,不处理发向this的信号
    d->connections.remove(this);
    
    // 第五阶段:取消所有注册的定时器
    if (d->threadData->eventDispatcher.loadAcquire())
        d->threadData->eventDispatcher.loadAcquire()->unregisterTimers(this);
    
    // 第六阶段:元对象系统清理
    if (d->isQObject) {
        // 从全局对象注册表中移除
        QMetaObject::removeGuard(this);
    }
}

1.2 deleteChildren:递归链的核心

cpp 复制代码
// qtbase/src/corelib/kernel/qobject_p.h
void QObjectPrivate::deleteChildren(QObjectPrivate *d)
{
    // 拷贝children列表,因为在delete过程中列表会被修改
    const QObjectList &children = d->children;
    // 必须倒序删除------先构造的后析构(栈式语义)
    for (int i = children.count() - 1; i >= 0; --i) {
        children.at(i)->deleteLater();
    }
    // deleteLater会异步执行,这里用while等待所有子对象析构完成
    while (!children.isEmpty()) {
        QCoreApplication::processEvents(
            QEventLoop::ExcludeUserInputEvents | QEventLoop::ExcludeSocketNotifiers);
    }
}

关键发现

  1. 子对象使用deleteLater()而非直接delete,确保信号槽断开安全
  2. 倒序遍历------符合C++栈式析构语义,先构造的后析构
  3. processEvents循环等待所有子对象异步析构完成

二、父子关系维护机制

2.1 setParent的实现细节

cpp 复制代码
// qtbase/src/corelib/kernel/qobject.cpp
void QObject::setParent(QObject *parent)
{
    Q_D(QObject);
    
    // 不能把自己设为自己的父对象
    if (parent == this) {
        qWarning("QObject::setParent: Cannot set parent, new parent is same as this object");
        return;
    }
    
    d->setParent_helper(parent, nullptr, nullptr);
}

// qtbase/src/corelib/kernel/qobject.cpp (private)
void QObjectPrivate::setParent_helper(QObject *parent, 
    const QMetaObject *metaObject, Qt::HANDLE threadId)
{
    Q_Q(QObject);
    
    if (parent == q->parent())
        return;  // 相同父对象,无需操作
    
    // 从旧父对象中移除
    if (q->parent()) {
        QObjectPrivate::get(q->parent())->children.removeOne(q);
    }
    
    // 加入新父对象的children列表
    if (parent) {
        QObjectPrivate::get(parent)->children.append(q);
    }
    
    // 更新线程亲和性
    if (threadId) {
        // 子对象继承父对象的线程亲和性
        q->moveToThread(reinterpret_cast<QThread*>(threadId));
    }
    
    // 发送ChildAdded/ChildRemoved事件
    QEvent e(QEvent::ChildAdded);
    QCoreApplication::sendEvent(parent, &e);
}

2.2 线程亲和性约束

cpp 复制代码
// qtbase/src/corelib/kernel/qobject.cpp
void QObject::moveToThread(QThread *targetThread)
{
    Q_D(QObject);
    
    if (d->threadData->thread == targetThread) {
        return;  // 已在同一线程
    }
    
    // 核心约束:对象不能被移动到有父对象的线程
    // 因为父对象必须在同一线程中delete
    if (d->parent && d->parent->thread() != targetThread) {
        qWarning("QObject::moveToThread: "
                 "Cannot move objects with a parent");
        return;
    }
    
    // 更新threadData
    QThreadData *currentData = QThreadData::get2(QThread::currentThread());
    QThreadData *targetData = QThreadData::get2(targetThread);
    
    if (d->threadData != currentData) {
        qWarning("QObject::moveToThread: "
                 "Current thread is not the object's thread");
        return;
    }
    
    d->threadData = targetData;
    
    // 注册到目标线程的事件分发器
    if (targetData->eventDispatcher.loadAcquire())
        targetData->eventDispatcher.loadAcquire()->registerTimer(
            d->timerId, Qt::PreciseTimer, q);
}

核心规则:有父对象的对象不能跨线程移动。这意味着对象树的析构链路始终在同一线程中执行。


三、deleteLater的异步析构机制

3.1 deleteLater源码

cpp 复制代码
// qtbase/src/corelib/kernel/qobject.cpp
void QObject::deleteLater()
{
    Q_D(QObject);
    
    if (d->deleteLaterCalled.loadRelaxed())
        return;  // 防止重复调用
    
    d->deleteLaterCalled.storeRelaxed(true);
    
    // 构造DeferredDelete事件并投递到当前线程的事件队列
    QCoreApplication::postEvent(this, new QEvent(QEvent::DeferredDelete));
}

关键postEvent将事件投递到对象所属线程的事件队列,而非当前线程。

3.2 DeferredDelete事件的处理

cpp 复制代码
// qtbase/src/corelib/kernel/qobject.cpp
bool QObject::event(QEvent *e)
{
    Q_D(QObject);
    
    switch (e->type()) {
    case QEvent::DeferredDelete:
        // 只有在对象所属线程中才能处理
        if (d->threadData->thread == QThread::currentThread()) {
            // 检查是否可以安全析构
            if (!d->deleteLaterCalled.loadRelaxed() || d->inDeleteLater) {
                return true;
            }
            d->inDeleteLater = true;
            
            // 调用delete------回到析构函数的链式反应
            delete this;
            return true;  // 注意:delete后this已无效,但return不会访问
        }
        // 不在所属线程,重新投递
        QCoreApplication::postEvent(this, new QEvent(QEvent::DeferredDelete));
        break;
    // ... 其他事件处理
    }
    return false;
}

3.3 deleteLater的安全性保证

复制代码
线程A:new Worker(this)        // Worker在主线程创建
线程B:worker->deleteLater()   // DeferredDelete投递到主线程事件队列
主线程事件循环:
  → 处理DeferredDelete
  → 检查当前线程 == Worker所属线程 ✓
  → delete Worker
  → Worker::~QObject()
  → 从this的children中移除Worker
  → delete Worker的子对象
  → 断开Worker的所有信号槽

四、与std智能指针的协同

4.1 问题:为什么不能直接用shared_ptr管理QObject?

cpp 复制代码
// ❌ 危险:shared_ptr + QObject对象树冲突
auto parent = std::make_shared<QObject>();
auto child = std::make_shared<QObject>(parent.get());

// 对象树关系:parent拥有child(会delete child)
// shared_ptr关系:parent和child各自有独立的引用计数
// 如果parent的shared_ptr先释放,QObject对象树会delete child
// 但child的shared_ptr还在,指向已释放的内存 → UAF

4.2 方案:QScopedPointer + 自定义Deleter

cpp 复制代码
// 自定义删除器:调用deleteLater而非直接delete
struct QObjectLaterDeleter {
    void operator()(QObject *obj) const {
        if (obj) {
            obj->deleteLater();  // 异步安全删除
        }
    }
};

// 使用方式
using QObjectPtr = QScopedPointer<QObject, QObjectLaterDeleter>;

class MyWidget : public QWidget {
    Q_OBJECT
public:
    MyWidget(QWidget *parent = nullptr) : QWidget(parent) {
        // worker会在事件循环中安全析构
        m_worker.reset(new Worker(this));
    }
    
private:
    QObjectPtr m_worker;
};

4.3 方案:QSharedPointer + QObject对象树集成

Qt 5提供了QObject::deleteLaterQSharedPointer的桥接:

cpp 复制代码
// qtbase/src/corelib/kernel/qsharedpointer_impl.h
template <class T>
class QSharedPointer
{
public:
    // 当引用计数归零时,调用T的析构
    ~QSharedPointer() {
        if (d && d->strongref.loadRelaxed() == 0 && !d->weakref.loadRelaxed()) {
            T *ptr = static_cast<T*>(d->ptr);
            // 对于QObject子类,使用deleteLater
            if constexpr (std::is_base_of_v<QObject, T>) {
                ptr->deleteLater();
            } else {
                delete ptr;
            }
            delete d;
        }
    }
};

但QSharedPointer本身与对象树仍有冲突。更优雅的方案:

4.4 完整方案:SmartQObject混合管理器

cpp 复制代码
// 智能QObject管理器:统一对象树与引用计数的生命周期
class SmartQObjectManager : public QObject
{
    Q_OBJECT
public:
    static SmartQObjectManager& instance()
    {
        static SmartQObjectManager mgr;
        return mgr;
    }
    
    // 创建对象并绑定到管理器
    template<typename T, typename... Args>
    QSharedPointer<T> create(Args&&... args)
    {
        T *raw = new T(std::forward<Args>(args)...);
        QSharedPointer<T> ptr(raw, [](T *obj) {
            // QSharedPointer引用计数归零时,不直接delete
            // 而是通知管理器可以回收
            SmartQObjectManager::instance().scheduleCleanup(obj);
        });
        
        // 注册到跟踪表
        registerObject(raw, ptr);
        return ptr;
    }
    
    // 为QSharedPointer对象设置对象树父节点
    void setParent(QSharedPointer<QObject> child, QObject *parent)
    {
        QMutexLocker locker(&m_mutex);
        child->setParent(parent);
        // 对象树拥有所有权,标记为树管理
        auto it = m_registry.find(child.data());
        if (it != m_registry.end()) {
            it->treeOwned = true;
        }
    }

private slots:
    void processCleanup()
    {
        QMutexLocker locker(&m_mutex);
        for (auto it = m_cleanupQueue.begin(); it != m_cleanupQueue.end(); ) {
            QObject *obj = *it;
            auto regIt = m_registry.find(obj);
            
            if (regIt == m_registry.end()) {
                it = m_cleanupQueue.erase(it);
                continue;
            }
            
            // 检查引用计数和对象树状态
            bool noSharedRefs = regIt->weakPtr.expired();
            bool noTreeParent = (obj->parent() == nullptr || !regIt->treeOwned);
            
            if (noSharedRefs && noTreeParent) {
                // 两个所有权来源都释放了,安全deleteLater
                obj->deleteLater();
                m_registry.erase(regIt);
                it = m_cleanupQueue.erase(it);
            } else {
                ++it;
            }
        }
    }

private:
    struct ObjectRegistry {
        std::weak_ptr<QObject> weakPtr;
        bool treeOwned{false};
        bool scheduled{false};
    };
    
    void registerObject(QObject *obj, QSharedPointer<QObject> ptr)
    {
        QMutexLocker locker(&m_mutex);
        ObjectRegistry entry;
        entry.weakPtr = ptr.toWeakRef();
        m_registry[obj] = entry;
    }
    
    void scheduleCleanup(QObject *obj)
    {
        QMutexLocker locker(&m_mutex);
        m_cleanupQueue.append(obj);
        // 触发异步清理
        QTimer::singleShot(0, this, &SmartQObjectManager::processCleanup);
    }
    
    QHash<QObject*, ObjectRegistry> m_registry;
    QList<QObject*> m_cleanupQueue;
    QMutex m_mutex;
};

五、多线程场景的内存陷阱与防护

5.1 陷阱一:子线程创建的QObject

cpp 复制代码
// ❌ 危险:子线程创建的QObject没有事件循环
QThread *thread = QThread::create([]() {
    auto *obj = new QObject();  // 线程亲和性为子线程
    // 子线程结束后,obj无法被deleteLater处理
    // 因为子线程没有事件循环
});
thread->start();
thread->wait();
// obj泄漏!

修复:

cpp 复制代码
// ✅ 正确:确保子线程有事件循环
QThread *workerThread = new QThread();
workerThread->start();

Worker *worker = new Worker();  // 在主线程创建
worker->moveToThread(workerThread);  // 移动到工作线程

// 连接finished信号自动清理
connect(workerThread, &QThread::finished, worker, &QObject::deleteLater);
connect(workerThread, &QThread::finished, workerThread, &QObject::deleteLater);

// 工作完成后
connect(worker, &Worker::workDone, workerThread, &QThread::quit);

5.2 陷阱二:信号槽连接导致的意外生命周期

cpp 复制代码
// ❌ 问题场景:Qt::DirectConnection跨线程
class Producer : public QObject {
    Q_OBJECT
signals:
    void dataReady(const QByteArray &data);
};

class Consumer : public QObject {
    Q_OBJECT
public slots:
    void onData(const QByteArray &data) {
        // 如果用DirectConnection,此slot在Producer线程执行
        // 如果Consumer此时被主线程delete...
    }
};

// Producer线程
Producer *p = new Producer();
Consumer *c = new Consumer();  // 主线程

connect(p, &Producer::dataReady, c, &Consumer::onData, 
        Qt::DirectConnection);  // 危险!跨线程DirectConnection

// 主线程delete c
delete c;  // p的线程可能同时在调用c->onData() → UAF

修复:

cpp 复制代码
// ✅ 自动连接类型 + 生命周期跟踪
connect(p, &Producer::dataReady, c, &Consumer::onData,
        Qt::QueuedConnection);  // 队列连接,始终在c的线程执行

// 或者使用QPointer安全引用
QPointer<Consumer> safeConsumer = c;
connect(p, &Producer::dataReady, this, [safeConsumer](const QByteArray &data) {
    if (safeConsumer) {
        safeConsumer->onData(data);
    }
});

5.3 陷阱三:对象树中的循环引用

cpp 复制代码
// ❌ 理论上的循环引用(Qt对象树中不会发生,但信号槽会)
class A : public QObject {
    Q_OBJECT
public:
    void setup() {
        B *b = new B(this);  // A是B的父对象
        connect(this, &A::sig, b, &B::slot);  // 正常:b断开连接在析构时
    }
};

// ❌ 真正的危险:互相信号连接形成引用环
class A : public QObject { /* ... */ };
class B : public QObject { /* ... */ };

A *a = new A;
B *b = new B;
// 信号互相连接 → 对象树析构时可以处理,但shared_ptr不行
connect(a, &A::sig, b, &B::slot);
connect(b, &B::sig, a, &A::slot);
// Qt对象树中:delete a → a析构 → a的children中没有b → b仍存活
// 如果b也没有父对象,泄漏

防护策略:

cpp 复制代码
// 引入生命周期管理器
class LifecycleManager : public QObject
{
    Q_OBJECT
public:
    void registerGroup(QObject *primary, QObject *secondary)
    {
        QMutexLocker locker(&m_mutex);
        m_groups[primary] = secondary;
        m_groups[secondary] = primary;
        
        // 任意一个析构时,另一个也析构
        connect(primary, &QObject::destroyed, this, [this, secondary]() {
            QMutexLocker lk(&m_mutex);
            m_groups.remove(secondary);
            secondary->deleteLater();
        });
    }
    
private:
    QHash<QObject*, QObject*> m_groups;
    QMutex m_mutex;
};

六、工业级内存泄漏检测架构

6.1 编译期对象跟踪

cpp 复制代码
// 通过宏注入跟踪代码(仅Debug模式)
#ifdef QT_DEBUG
#define TRACK_OBJECT(Class) \
    static int _trackCount = 0; \
    _trackCount++; \
    qDebug() << "CREATE" << #Class << "count:" << _trackCount; \
    connect(this, &QObject::destroyed, this, []() { \
        _trackCount--; \
        qDebug() << "DESTROY" << #Class << "count:" << _trackCount; \
    });

// 在构造函数中使用
class MyWidget : public QWidget {
    Q_OBJECT
public:
    MyWidget(QWidget *parent = nullptr) : QWidget(parent) {
        TRACK_OBJECT(MyWidget)
    }
};
#endif

6.2 运行时对象树快照

cpp 复制代码
class ObjectTreeSnapshot : public QObject
{
    Q_OBJECT
public:
    struct ObjectInfo {
        void *address;
        const char *className;
        void *parentAddress;
        int childCount;
        QString objectName;
        Qt::HANDLE threadId;
    };
    
    // 拍摄当前时刻的对象树快照
    QMap<void*, ObjectInfo> takeSnapshot()
    {
        QMap<void*, ObjectInfo> result;
        collectObjects(nullptr, result);
        return result;
    }
    
    // 与之前快照对比,找出差异
    QVector<ObjectInfo> diffWithPrevious(const QMap<void*, ObjectInfo> &previous,
                                          const QMap<void*, ObjectInfo> &current)
    {
        QVector<ObjectInfo> leaked;
        
        for (auto it = previous.begin(); it != previous.end(); ++it) {
            if (!current.contains(it.key())) {
                // 已析构,正常
            }
        }
        
        for (auto it = current.begin(); it != current.end(); ++it) {
            if (!previous.contains(it.key())) {
                // 新增对象
                leaked.append(it.value());
            }
        }
        
        return leaked;
    }

private:
    void collectObjects(QObject *root, QMap<void*, ObjectInfo> &result)
    {
        QObjectList objects = root ? root->children() : 
            QApplication::topLevelWidgets();
        
        for (QObject *obj : objects) {
            ObjectInfo info;
            info.address = obj;
            info.className = obj->metaObject()->className();
            info.parentAddress = obj->parent();
            info.childCount = obj->children().count();
            info.objectName = obj->objectName();
            info.threadId = QThread::currentThreadId();
            result[obj] = info;
            
            collectObjects(obj, result);  // 递归
        }
    }
};

6.3 定期泄漏检测定时器

cpp 复制代码
// 在应用中集成定期泄漏检测
class LeakDetector : public QObject
{
    Q_OBJECT
public:
    explicit LeakDetector(QObject *parent = nullptr) : QObject(parent)
    {
        m_snapshot = m_dumper.takeSnapshot();
        
        // 每30秒检测一次
        m_timer.setInterval(30000);
        connect(&m_timer, &QTimer::timeout, this, &LeakDetector::checkLeaks);
        m_timer.start();
    }
    
private slots:
    void checkLeaks()
    {
        auto current = m_dumper.takeSnapshot();
        auto diff = m_dumper.diffWithPrevious(m_snapshot, current);
        
        if (diff.size() > m_snapshot.size() * 0.1 + 5) {
            // 新增对象超过10%+5,可能存在泄漏
            qWarning() << "Possible memory leak detected:"
                        << diff.size() << "new objects";
            for (const auto &obj : diff) {
                qWarning() << "  +" << obj.className
                           << obj.objectName
                           << "thread:" << obj.threadId;
            }
        }
        
        // 每5分钟更新基线
        if (++m_checkCount % 10 == 0) {
            m_snapshot = current;
        }
    }

private:
    ObjectTreeSnapshot m_dumper;
    QMap<void*, ObjectTreeSnapshot::ObjectInfo> m_snapshot;
    QTimer m_timer;
    int m_checkCount{0};
};

七、实战架构:大型应用的内存管理框架

7.1 分层对象管理策略

cpp 复制代码
// 大型Qt应用的推荐对象管理架构
class ApplicationObjectManager : public QObject
{
    Q_OBJECT
public:
    // 会话级对象:用户登录时创建,登出时销毁
    void createSessionObjects()
    {
        m_sessionRoot = new QObject(this);
        m_sessionRoot->setObjectName("SessionRoot");
        
        // 所有会话对象挂载到sessionRoot下
        m_userProfile = new UserProfile(m_sessionRoot);
        m_connectionManager = new ConnectionManager(m_sessionRoot);
        m_dataCache = new DataCache(m_sessionRoot);
        
        // sessionRoot析构时,所有子对象自动清理
        connect(AuthService::instance(), &AuthService::loggedOut,
                m_sessionRoot, &QObject::deleteLater);
    }
    
    // 功能模块级对象:动态加载/卸载
    void loadModule(const QString &moduleName)
    {
        QObject *moduleRoot = new QObject(this);
        moduleRoot->setObjectName("Module_" + moduleName);
        
        // 模块卸载时清理
        connect(ModuleLoader::instance(), &ModuleLoader::moduleUnloaded,
                moduleRoot, [moduleRoot, moduleName](const QString &name) {
                    if (name == moduleName)
                        moduleRoot->deleteLater();
                });
        
        m_moduleRoots[moduleName] = moduleRoot;
    }
    
    // 获取模块的对象容器
    QObject *moduleContainer(const QString &moduleName) const
    {
        return m_moduleRoots.value(moduleName, nullptr);
    }

private:
    QObject *m_sessionRoot{nullptr};
    QMap<QString, QObject*> m_moduleRoots;
    
    // 会话对象指针(弱引用,由对象树管理生命周期)
    QPointer<UserProfile> m_userProfile;
    QPointer<ConnectionManager> m_connectionManager;
    QPointer<DataCache> m_dataCache;
};

7.2 线程安全的对象工厂

cpp 复制代码
// 线程安全的对象创建,确保正确的线程亲和性
template<typename T>
class ThreadSafeObjectFactory
{
public:
    // 在目标线程中创建对象
    static QSharedPointer<T> createInThread(QThread *targetThread,
                                             QObject *parent = nullptr)
    {
        QSharedPointer<T> result;
        QEventLoop loop;
        
        // 通过信号槽将创建操作投递到目标线程
        QTimer::singleShot(0, targetThread, [&]() {
            T *obj = new T(parent);
            // 如果没有parent,创建一个隐藏的根对象作为生命周期管理者
            if (!parent) {
                QObject *guard = new QObject();
                guard->moveToThread(targetThread);
                obj->setParent(guard);
                // guard由QSharedPointer管理
                result = QSharedPointer<T>(obj);
            } else {
                result = QSharedPointer<T>(obj);
            }
            loop.quit();
        });
        
        loop.exec();
        return result;
    }
    
    // 使用示例:
    // auto dbWorker = ThreadSafeObjectFactory<DatabaseWorker>::createInThread(dbThread);
};

八、性能考量:对象树析构的成本

8.1 析构时间测试

cpp 复制代码
// 测试不同规模对象树的析构时间
void benchmarkObjectTree()
{
    const int sizes[] = {100, 1000, 10000, 100000};
    
    for (int size : sizes) {
        QElapsedTimer timer;
        timer.start();
        
        // 构建对象树
        QObject *root = new QObject();
        for (int i = 0; i < size; i++) {
            new QObject(root);
        }
        
        qint64 buildTime = timer.elapsed();
        
        // 析构
        timer.restart();
        delete root;
        qint64 destroyTime = timer.elapsed();
        
        qDebug() << "Size:" << size
                 << "Build:" << buildTime << "ms"
                 << "Destroy:" << destroyTime << "ms";
    }
}

8.2 优化大型对象树的析构

cpp 复制代码
// 对于超大规模对象树(>10万节点),分批析构
class BatchDestructor : public QObject
{
    Q_OBJECT
public:
    void destroyTree(QObject *root, int batchSize = 100)
    {
        QObjectList children = root->children();
        
        // 分批deleteLater
        for (int i = 0; i < children.size(); i += batchSize) {
            int end = qMin(i + batchSize, children.size());
            for (int j = i; j < end; j++) {
                children[j]->deleteLater();
            }
            
            // 让事件循环处理一批后再处理下一批
            QCoreApplication::processEvents();
        }
        
        // 最后删除根
        root->deleteLater();
    }
};

九、架构总结

9.1 内存管理决策矩阵

场景 推荐方案 线程安全 性能
对象有明确的父节点 对象树(setParent) ✅(同线程) ⭐⭐⭐
需要共享所有权 QSharedPointer + deleteLater ⭐⭐
跨线程传递对象 moveToThread + QPointer ⭐⭐⭐
临时对象 QScopedPointer + 自定义Deleter ⭐⭐⭐⭐
信号槽参数中的对象 QPointer(弱引用) ⭐⭐⭐⭐

9.2 核心原则

  1. 对象树是第一选择------简单、自动、线程安全(同线程内)
  2. deleteLater永远优于delete------避免信号槽断开时的竞态
  3. QPointer是跨线程安全的唯一指针------析构时自动置null
  4. QSharedPointer与对象树不能混用------除非有统一的管理层
  5. 定期泄漏检测------ObjectTreeSnapshot是最后防线

《注:若有发现问题欢迎大家提出来纠正》

相关推荐
zhaqonianzhu1 小时前
LOL切回桌面问题,采用监控抓出元凶方式
开发语言
Aurorar0rua1 小时前
CS50 x 2024 Notes Arrays - 04
c语言·开发语言·学习方法
AI科技星1 小时前
数术江湖·全卷合集 - 硬核江湖・数理史诗
android·人工智能·架构·概率论·学习方法
小庞在加油2 小时前
从qmake到CMake+VSCode:Qt项目现代化迁移与AI提效实战指南
vscode·qt·ai·ai工具
John_ToDebug2 小时前
Chromium 132→148 升级实战:Legacy IPC 消息丢失问题深度解析
c++·chrome·ai·架构
一起吃元宵2 小时前
百度网盘下载不限速的办法_百度网盘不限速
开发语言·百度网盘·下载不限速·不限速·百度网盘不限速
人道领域2 小时前
【LeetCode刷题日记】47.全排列Ⅱ
java·开发语言·算法·leetcode
恼书:-(空寄2 小时前
接口乱改直接炸线上!微服务接口版本控制全方案:URL_请求头版本+接口兼容原则,老旧系统无痛迭代
微服务·架构
ch3nyuyu2 小时前
socket套接字
开发语言·php