Qt-内存管理机制

父子对象

        C++中派生类构造顺序:先执行基类的构造函数,再执行派生类的构造函数;析构时,先执行派生类的析构函数,再执行基类的析构函数。

        这一部分的内容与qt 对象管理_~怎么回事啊~的博客-CSDN博客

构造过程

继承QWidget的派生类

        首先看一下一个继承QWidget的派生类qttest的构造过程发生了什么:

        首先调用基类QWidget的构造函数:在QWidget的构造函数中,会调用QWidget的基类QObject的构造函数,QObject(*new QWidgetPrivate, 0)

QWidget::QWidget(QWidget *parent, Qt::WindowFlags f)
    : QObject(*new QWidgetPrivate, 0), QPaintDevice()
{
    QT_TRY {
        d_func()->init(parent, f);
    } QT_CATCH(...) {
        QWidgetExceptionCleaner::cleanup(this, d_func());
        QT_RETHROW;
    }
}

        任何继承或间接继承QObject的对象构造时,都首先会调用QObject的构造函数QObject::QObject(QObjectPrivate &dd, QObject *parent),在QWidget构造函数中,第一个参数传入的是QWidgetPrivate,第二个参数parent传入的0。由于parent传入0,QObject中仅仅做了:

   d_ptr指向QWidgetPrivate,同时将 d_ptr->q_ptr = this;

QObject::QObject(QObjectPrivate &dd, QObject *parent)
    : d_ptr(&dd)
{
    Q_D(QObject);
    d_ptr->q_ptr = this;
    //1 获取线程数据
    d->threadData = (parent && !parent->thread()) ? parent->d_func()->threadData : QThreadData::current();
    d->threadData->ref();
    //2 如果parent存在
    if (parent) {
        QT_TRY {
            if (!check_parent_thread(parent, parent ? parent->d_func()->threadData : 0, d->threadData))
                parent = 0;
            if (d->isWidget) {
                if (parent) {
                    d->parent = parent;
                    d->parent->d_func()->children.append(this);
                }
                // no events sent here, this is done at the end of the QWidget constructor
            } else {
                setParent(parent);
            }
        } QT_CATCH(...) {
            d->threadData->deref();
            QT_RETHROW;
        }
    }
#if QT_VERSION < 0x60000
    qt_addObject(this);
#endif
    if (Q_UNLIKELY(qtHookData[QHooks::AddQObject]))
        reinterpret_cast(qtHookData[QHooks::AddQObject])(this);
}

然后分析QWidget的构造函数的中操作:        d_func()->init(parent, f);


void QWidgetPrivate::init(QWidget *parentWidget, Qt::WindowFlags f)
{
    Q_Q(QWidget);
    if (Q_UNLIKELY(!qobject_cast(QCoreApplication::instance())))
        qFatal("QWidget: Cannot create a QWidget without QApplication");

    Q_ASSERT(allWidgets);
    // 将q指针插入到 QWidgetSet *QWidgetPrivate::allWidgets = 0;         // widgets with no wid
    if (allWidgets)
        allWidgets->insert(q);

    int targetScreen = -1;
    if (parentWidget && parentWidget->windowType() == Qt::Desktop) {
        const QDesktopScreenWidget *sw = qobject_cast(parentWidget);
        targetScreen = sw ? sw->screenNumber() : 0;
        parentWidget = 0;
    }

    q->data = &data;

    //判断是不是在主线程的创建,"Widgets must be created in the GUI thread.
#ifndef QT_NO_THREAD
    if (!parent) {
        Q_ASSERT_X(q->thread() == qApp->thread(), "QWidget",
                   "Widgets must be created in the GUI thread.");
    }
#endif

    
    if (targetScreen >= 0) {
        topData()->initialScreenIndex = targetScreen;
        if (QWindow *window = q->windowHandle())
            window->setScreen(QGuiApplication::screens().value(targetScreen, Q_NULLPTR));
    }

    //data赋初始值
    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_sys() 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);
    QApplication::sendEvent(q, &e);
    QApplication::postEvent(q, new QEvent(QEvent::PolishRequest));

    extraPaintEngine = 0;

}

析构过程


/*!
    Destroys the widget.

    All this widget's children are deleted first. The application
    exits if this widget is the main widget.
*/

QWidget::~QWidget()
{
    Q_D(QWidget);
    d->data.in_destructor = true;

#if defined (QT_CHECK_STATE)
    if (Q_UNLIKELY(paintingActive()))
        qWarning("QWidget: %s (%s) deleted while being painted", className(), name());
#endif

#ifndef QT_NO_GESTURES
    if (QGestureManager *manager = QGestureManager::instance()) {
        // \forall Qt::GestureType type : ungrabGesture(type) (inlined)
        for (auto it = d->gestureContext.keyBegin(), end = d->gestureContext.keyEnd(); it != end; ++it)
            manager->cleanupCachedGestures(this, *it);
    }
    d->gestureContext.clear();
#endif

    // force acceptDrops false before winId is destroyed.
    d->registerDropSite(false);



#ifndef QT_NO_ACTION
    // remove all actions from this widget
    for (int i = 0; i < d->actions.size(); ++i) {
        QActionPrivate *apriv = d->actions.at(i)->d_func();
        apriv->widgets.removeAll(this);
    }
    d->actions.clear();
#endif

#ifndef QT_NO_SHORTCUT
    // Remove all shortcuts grabbed by this
    // widget, unless application is closing
    if (!QApplicationPrivate::is_app_closing && testAttribute(Qt::WA_GrabbedShortcut))
        qApp->d_func()->shortcutMap.removeShortcut(0, this, QKeySequence());
#endif

    // delete layout while we still are a valid widget
    delete d->layout;
    d->layout = 0;
    // Remove myself from focus list

    Q_ASSERT(d->focus_next->d_func()->focus_prev == this);
    Q_ASSERT(d->focus_prev->d_func()->focus_next == this);

    if (d->focus_next != this) {
        d->focus_next->d_func()->focus_prev = d->focus_prev;
        d->focus_prev->d_func()->focus_next = d->focus_next;
        d->focus_next = d->focus_prev = 0;
    }


    QT_TRY {
#if QT_CONFIG(graphicsview)
        const QWidget* w = this;
        while (w->d_func()->extra && w->d_func()->extra->focus_proxy)
            w = w->d_func()->extra->focus_proxy;
        QWidget *window = w->window();
        QWExtra *e = window ? window->d_func()->extra : 0;
        if (!e || !e->proxyWidget || (w->parentWidget() && w->parentWidget()->d_func()->focus_child == this))
#endif
        clearFocus();
    } QT_CATCH(...) {
        // swallow this problem because we are in a destructor
    }

    d->setDirtyOpaqueRegion();

    if (isWindow() && isVisible() && internalWinId()) {
        QT_TRY {
            d->close_helper(QWidgetPrivate::CloseNoEvent);
        } QT_CATCH(...) {
            // if we're out of memory, at least hide the window.
            QT_TRY {
                hide();
            } QT_CATCH(...) {
                // and if that also doesn't work, then give up
            }
        }
    }

#if 0 /* Used to be included in Qt4 for Q_WS_WIN */ || 0 /* Used to be included in Qt4 for Q_WS_X11 */|| 0 /* Used to be included in Qt4 for Q_WS_MAC */
    else if (!internalWinId() && isVisible()) {
        qApp->d_func()->sendSyntheticEnterLeave(this);
    }
#endif
    else if (isVisible()) {
        qApp->d_func()->sendSyntheticEnterLeave(this);
    }

    if (QWidgetBackingStore *bs = d->maybeBackingStore()) {
        bs->removeDirtyWidget(this);
        if (testAttribute(Qt::WA_StaticContents))
            bs->removeStaticWidget(this);
    }

    delete d->needsFlush;
    d->needsFlush = 0;

    // The next 20 lines are duplicated from QObject, but required here
    // since QWidget deletes is children itself
    bool blocked = d->blockSig;
    d->blockSig = 0; // unblock signals so we always emit destroyed()

    if (d->isSignalConnected(0)) {
        QT_TRY {
            emit destroyed(this);
        } QT_CATCH(...) {
            // all the signal/slots connections are still in place - if we don't
            // quit now, we will crash pretty soon.
            qWarning("Detected an unexpected exception in ~QWidget while emitting destroyed().");
            QT_RETHROW;
        }
    }

    if (d->declarativeData) {
        if (static_cast(d->declarativeData)->ownedByQml1) {
            if (QAbstractDeclarativeData::destroyed_qml1)
                QAbstractDeclarativeData::destroyed_qml1(d->declarativeData, this);
        } else {
            if (QAbstractDeclarativeData::destroyed)
                QAbstractDeclarativeData::destroyed(d->declarativeData, this);
        }
        d->declarativeData = 0;                 // don't activate again in ~QObject
    }

    d->blockSig = blocked;

#if 0 // Used to be included in Qt4 for Q_WS_MAC
    // QCocoaView holds a pointer back to this widget. Clear it now
    // to make sure it's not followed later on. The lifetime of the
    // QCocoaView might exceed the lifetime of this widget in cases
    // where Cocoa itself holds references to it.
    extern void qt_mac_clearCocoaViewQWidgetPointers(QWidget *);
    qt_mac_clearCocoaViewQWidgetPointers(this);
#endif

    //清空孩子
    if (!d->children.isEmpty())
        d->deleteChildren();

    QApplication::removePostedEvents(this);

    QT_TRY {
        destroy();                                        // platform-dependent cleanup
    } QT_CATCH(...) {
        // if this fails we can't do anything about it but at least we are not allowed to throw.
    }
    --QWidgetPrivate::instanceCounter;

    if (QWidgetPrivate::allWidgets) // might have been deleted by ~QApplication
        QWidgetPrivate::allWidgets->remove(this);

    QT_TRY {
        QEvent e(QEvent::Destroy);
        QCoreApplication::sendEvent(this, &e);
    } QT_CATCH(const std::exception&) {
        // if this fails we can't do anything about it but at least we are not allowed to throw.
    }

#if QT_CONFIG(graphicseffect)
    delete d->graphicsEffect;
#endif
}

直接继承QObject的类

        调用的QOject的构造函数

QObject::QObject(QObject *parent)
    : d_ptr(new QObjectPrivate)
{
    Q_D(QObject);
    d_ptr->q_ptr = this;

   //1 如果当前派生对象存在父对象将获取父对象的线程数据,否则获取派生对象所在的线程数据
    d->threadData = (parent && !parent->thread()) ? parent->d_func()->threadData : QThreadData::current();
    d->threadData->ref();

    //2 如果派生对象存在父对象
    if (parent) {
        QT_TRY {
            //检查派生对象和父对象是否在同一个线程中
            if (!check_parent_thread(parent, parent ? parent->d_func()->threadData : 0, d->threadData))
                parent = 0;//如果不在同一个线程中,则将父对象置空(父子对象必须在一个线程中)
            setParent(parent);//为当前派生对象设置父对象
        } QT_CATCH(...) {
            d->threadData->deref();
            QT_RETHROW;
        }
    }
#if QT_VERSION < 0x60000
    qt_addObject(this);
#endif
    if (Q_UNLIKELY(qtHookData[QHooks::AddQObject]))
        reinterpret_cast(qtHookData[QHooks::AddQObject])(this);
}

void QObject::setParent(QObject *parent)
{
    Q_D(QObject);
    Q_ASSERT(!d->isWidget);
    d->setParent_helper(parent);
}


void QObjectPrivate::setParent_helper(QObject *o)
{
    Q_Q(QObject);
    //1 检查需要设置的父对象o 是否和当前已经设置的父对象parent相同,相同则返回
    if (o == parent)
        return;
    //2 如果当前派生对象已经存在父对象,则需要从当前的父对象列表中移除
    if (parent) {
        QObjectPrivate *parentD = parent->d_func();
        if (parentD->isDeletingChildren && wasDeleted
            && parentD->currentChildBeingDeleted == q) {
            // don't do anything since QObjectPrivate::deleteChildren() already
            // cleared our entry in parentD->children.
        } else {
            //获取当前派生对象在父对象中的孩子列表中的索引
            const int index = parentD->children.indexOf(q);
            if (parentD->isDeletingChildren) {
                parentD->children[index] = 0;
            } else {
                 //根据索引将派生对象从子对象列表中删除
                parentD->children.removeAt(index);
                if (sendChildEvents && parentD->receiveChildEvents) {
                    //移除后,创建QEvent::ChildRemoved 并发送给派生类的当前父对象
                    QChildEvent e(QEvent::ChildRemoved, q);
                    QCoreApplication::sendEvent(parent, &e);
                }
            }
        }
    }

    //3 派生对象重置父对象为o
    parent = o;
    if (parent) {
        // object hierarchies are constrained to a single thread
        // 检查当前线程的线程数据是否和父对象的线程数据一致(父子对象必须在同一个线程中)
        if (threadData != parent->d_func()->threadData) {
            qWarning("QObject::setParent: Cannot set parent, new parent is in a different thread");
            parent = 0;
            return;
        }

       //父对象的孩子列表中添加派生对象
        parent->d_func()->children.append(q);
        if(sendChildEvents && parent->d_func()->receiveChildEvents) {
            if (!isWidget) {
                //发送ChildAdded 事件到父对象
                QChildEvent e(QEvent::ChildAdded, q);
                QCoreApplication::sendEvent(parent, &e);
            }
        }
    }
    if (!wasDeleted && !isDeletingChildren && declarativeData && QAbstractDeclarativeData::parentChanged)
        QAbstractDeclarativeData::parentChanged(declarativeData, q, o);
}

父类对QEvent::ChildAdded与QEvent::ChildRemoved事件的处理过程


bool QObject::event(QEvent *e)
{
    switch (e->type()) {
    case QEvent::Timer:
        timerEvent((QTimerEvent*)e);
        break;

    case QEvent::ChildAdded:
    case QEvent::ChildPolished:
    case QEvent::ChildRemoved:
        childEvent((QChildEvent*)e);
        break;
......
}

//哈哈 不处理
void QObject::childEvent(QChildEvent * /* event */)
{
}

析构过程


QObject::~QObject()
{
    Q_D(QObject);
    d->wasDeleted = true;
    d->blockSig = 0; // unblock signals so we always emit destroyed()


    //1 获取当前对象的共享指针的对象
    QtSharedPointer::ExternalRefCountData *sharedRefcount = d->sharedRefcount.load();
    if (sharedRefcount) {
        if (sharedRefcount->strongref.load() > 0) {
            qWarning("QObject: shared QObject was deleted directly. The program is malformed and may crash.");
            // but continue deleting, it's too late to stop anyway
        }

        // indicate to all QWeakPointers that this QObject has now been deleted
        sharedRefcount->strongref.store(0);
        if (!sharedRefcount->weakref.deref())
            delete sharedRefcount;
    }


    //2 检测当前对象的类型
    if (!d->isWidget && d->isSignalConnected(0)) {
        emit destroyed(this);
    }

    //3 清理数据
    if (d->declarativeData) {
        if (static_cast(d->declarativeData)->ownedByQml1) {
            if (QAbstractDeclarativeData::destroyed_qml1)
                QAbstractDeclarativeData::destroyed_qml1(d->declarativeData, this);
        } else {
            if (QAbstractDeclarativeData::destroyed)
                QAbstractDeclarativeData::destroyed(d->declarativeData, this);
        }
    }


    //4 清理信号与槽
         //清理发送者
    // set ref to zero to indicate that this object has been deleted
    if (d->currentSender != 0)
        d->currentSender->ref = 0;
    d->currentSender = 0;

        //清理链接双向列表
    if (d->connectionLists || d->senders) {
        QMutex *signalSlotMutex = signalSlotLock(this);
        QMutexLocker locker(signalSlotMutex);

        // disconnect all receivers
        if (d->connectionLists) {
            ++d->connectionLists->inUse;
            int connectionListsCount = d->connectionLists->count();
            for (int signal = -1; signal < connectionListsCount; ++signal) {
                QObjectPrivate::ConnectionList &connectionList =
                    (*d->connectionLists)[signal];

                while (QObjectPrivate::Connection *c = connectionList.first) {
                    if (!c->receiver) {
                        connectionList.first = c->nextConnectionList;
                        c->deref();
                        continue;
                    }

                    QMutex *m = signalSlotLock(c->receiver);
                    bool needToUnlock = QOrderedMutexLocker::relock(signalSlotMutex, m);

                    if (c->receiver) {
                        *c->prev = c->next;
                        if (c->next) c->next->prev = c->prev;
                    }
                    c->receiver = 0;
                    if (needToUnlock)
                        m->unlock();

                    connectionList.first = c->nextConnectionList;

                    // The destroy operation must happen outside the lock
                    if (c->isSlotObject) {
                        c->isSlotObject = false;
                        locker.unlock();
                        c->slotObj->destroyIfLastRef();
                        locker.relock();
                    }
                    c->deref();
                }
            }

            if (!--d->connectionLists->inUse) {
                delete d->connectionLists;
            } else {
                d->connectionLists->orphaned = true;
            }
            d->connectionLists = 0;
        }

        /* Disconnect all senders:
         * This loop basically just does
         *     for (node = d->senders; node; node = node->next) { ... }
         *
         * We need to temporarily unlock the receiver mutex to destroy the functors or to lock the
         * sender's mutex. And when the mutex is released, node->next might be destroyed by another
         * thread. That's why we set node->prev to &node, that way, if node is destroyed, node will
         * be updated.
         */
        QObjectPrivate::Connection *node = d->senders;
        while (node) {
            QObject *sender = node->sender;
            // Send disconnectNotify before removing the connection from sender's connection list.
            // This ensures any eventual destructor of sender will block on getting receiver's lock
            // and not finish until we release it.
            sender->disconnectNotify(QMetaObjectPrivate::signal(sender->metaObject(), node->signal_index));
            QMutex *m = signalSlotLock(sender);
            node->prev = &node;
            bool needToUnlock = QOrderedMutexLocker::relock(signalSlotMutex, m);
            //the node has maybe been removed while the mutex was unlocked in relock?
            if (!node || node->sender != sender) {
                // We hold the wrong mutex
                Q_ASSERT(needToUnlock);
                m->unlock();
                continue;
            }
            node->receiver = 0;
            QObjectConnectionListVector *senderLists = sender->d_func()->connectionLists;
            if (senderLists)
                senderLists->dirty = true;

            QtPrivate::QSlotObjectBase *slotObj = Q_NULLPTR;
            if (node->isSlotObject) {
                slotObj = node->slotObj;
                node->isSlotObject = false;
            }

            node = node->next;
            if (needToUnlock)
                m->unlock();

            if (slotObj) {
                if (node)
                    node->prev = &node;
                locker.unlock();
                slotObj->destroyIfLastRef();
                locker.relock();
            }
        }
    }


    // 5 清理孩子列表
    if (!d->children.isEmpty())
        d->deleteChildren();

#if QT_VERSION < 0x60000
    qt_removeObject(this);
#endif
    if (Q_UNLIKELY(qtHookData[QHooks::RemoveQObject]))
        reinterpret_cast(qtHookData[QHooks::RemoveQObject])(this);

     // 6 设置父对象为0
    if (d->parent)        // remove it from parent object
        d->setParent_helper(0);
}

​
void QObjectPrivate::deleteChildren()
{
    Q_ASSERT_X(!isDeletingChildren, "QObjectPrivate::deleteChildren()", "isDeletingChildren already set, did this function recurse?");
    isDeletingChildren = true;
    // delete children objects
    // don't use qDeleteAll as the destructor of the child might
    // delete siblings
    for (int i = 0; i < children.count(); ++i) {
        currentChildBeingDeleted = children.at(i);
        children[i] = 0;
        delete currentChildBeingDeleted;
    }
    children.clear();
    currentChildBeingDeleted = 0;
    isDeletingChildren = false;
}

​

总结:

1 QObject 的派生类在构造函数中会调用QOject的构造函数,如果存在parent,会将派生类从之前的parent(如果存在)的childrenList中删除,并将派生类添加到新的parent(如果存在)的childrenList中。

2 父子对象必须在同一个线程中

3 qt的父子对象管理是半自动的父子对象管理,因为我们要为子对象手动设置父对象。析构过程 :子对象的 析构过程中,会将children列表中的它的子对象先释放,然后从 它的父对象列表中移除自己。

4 这种管理存在缺陷:父对象并没有区分子对象是分配在栈空间还是堆空间。如果子对象创建在栈空间且子对象创建在父对象的前面则会造成内存的重复释放进而造成crash 。如下列子

#include "qttest.h"
#include 
#include 
#inc;ude 


int main(int argc, char *argv[])
{
	QApplication a(argc, argv);

	QPushButton b;
	QWidget w;
	b.setParent(&w);
	w.show();
	return a.exec();
}

原因:分配在栈空间的对象的销毁顺序:从后向前,因此当程序退出时,先销毁QWidget,在QWidget的析构函数中,会将孩子列表中的指针 delete掉,但是QPushButton是分配在栈空间,delete会造成crash 。

        如果QPushButton的创建在QWidget的创建之后是没有问题的,当程序退出时,先销毁QPushButton,在QPushButton的析构函数中,会将QPushButton从父对象QWidget的孩子列表中移除。之后销毁QWidget时,孩子列表中已经没有QPushButton,顺利退出。

你可能感兴趣的:(qt,qt,开发语言,c++)