qt d指针和对象树

PIMPL

Pimpl技术的基本应用_XX風的博客-CSDN博客

Qt中D-Pointer的实现

在Object 的构造函数中:

QObject::QObject(QObject *parent)
    : d_ptr(new QObjectPrivate)
{
    Q_D(QObject);
    d_ptr->q_ptr = this;
    d->threadData = (parent && !parent->thread()) ? parent->d_func()->threadData : QThreadData::current();
    d->threadData->ref();
    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);
}

可以看到这样两句:

    Q_D(QObject);
    d_ptr->q_ptr = this;

d->threadData =....

他们的作用是什么呢?展开宏:

qtbase\src\corelib\kernel\qobject.h 在QObject的定义中有:

class Q_CORE_EXPORT QObject
{
......
    Q_DECLARE_PRIVATE(QObject)
......
protected:
    QScopedPointer d_ptr;
......
};

        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 deleteLaterCalled : 1;
    uint unused : 24;
    int postedEvents;
    QDynamicMetaObjectData *metaObject;
    QMetaObject *dynamicMetaObject() const;
};

Q_DECLARE_PRIVATE 宏的作用      

  展开宏Q_DECLARE_PRIVATE:qtbase\src\corelib\global\qglobal.h,可以看到它为当前类创建两个内联函数:一个是普通程序函数 d_func(),另一个是常量成员函数 d_func() const ;以及为当前类添加了友元类  friend class Class##Private;

        作用:定义了当前类的内联函数d_fun()并使其指向私有实现类,同时将私有类声明为当前类的友元类

template  static inline T *qGetPtrHelper(T *ptr) { return ptr; }
template  static inline typename Wrapper::pointer qGetPtrHelper(const Wrapper &p) { return p.data(); }

#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;

#define Q_DECLARE_PRIVATE_D(Dptr, Class) \
    inline Class##Private* d_func() { return reinterpret_cast(qGetPtrHelper(Dptr)); } \
    inline const Class##Private* d_func() const { return reinterpret_cast(qGetPtrHelper(Dptr)); } \
    friend class Class##Private;

#define Q_DECLARE_PUBLIC(Class)                                    \
    inline Class* q_func() { return static_cast(q_ptr); } \
    inline const Class* q_func() const { return static_cast(q_ptr); } \
    friend class Class;

#define Q_D(Class) Class##Private * const d = d_func()
#define Q_Q(Class) Class * const q = q_func()

Q_D的作用      

        在QObject 路径C:\Qt\5.9.8\msvc2015\include\QtCore\qobject.h 中定义了d_ptr指针:

protected:
    QScopedPointer d_ptr;

          可以看到d_ptr是一个QObjectData的智能指针,因为是protected权限的,所以QObject的所有子类都能访问它,d_func函数把它强制转换成当前类的QObjectPrivate指针类型(QObjectPrivate继承于QObjectData),并返回该指针,这样我们在每个QObject的子类中使用该宏就能完成转换。那么d_func这个函数在什么时候调用呢?接着看qobject.cpp文件的一个构造函数

QObject::QObject(QObject *parent)
    : d_ptr(new QObjectPrivate)
{
//Q_D的展开形式:
//#define Q_D(QObject) QObjectPrivate * const d = d_func()
//inline QObjectPrivate* d_func() { return reinterpret_cast(qGetPtrHelper(d_ptr)); }
//template  static inline typename Wrapper::pointer qGetPtrHelper(const Wrapper &p) { return p.data(); }   d_ptr 是一个智能指针
    Q_D(QObject);
//也就是 QObjectPrivate* const d = d_ptr.data();
    d_ptr->q_ptr = this;
    d->threadData = (parent && !parent->thread()) ? parent->d_func()->threadData : QThreadData::current();
    d->threadData->ref();

        到这里很明白了,d指针原来是通过宏Q_D获得的,也就是函数d_func的返回值,上文我们分析过了,d_func通过Q_DECLARE_PRIVATE宏来获得。

        利用d_func()函数,可以避免我们每次直接拿d_ptr指针进行类型转换(因为我们有可能会在子类中使用此方法,具体我们将在后面的拓展中详述)。 在d_func()中,我们为什么不直接使用d_ptr ,而要借助qGetPtrHelper()函数呢?利用此函数,是为了适配我们使用智能指针的情况,因为此时我们要拿到真正的指针,需要调用d_ptr.data()。 

        Q_D的使用方式和作用:

1 仅作用于当前函数作用域,在每个函数中第一行使用

2 定义了一个QObjectPrivate的常量指针指向d_fun(),因此可以通过Q_D访问d指针

3 d指针使用智能指针,因此无需手动释放

4 在QObject中Q_DECLARE_PRIVATE  和Q_D配合使用 是为了:方便获取d指针,d指针指向class#Private(私有类)

Q_DECLARE_PUBLIC与Q_Q

#define Q_DECLARE_PUBLIC(Class)                                    \
    inline Class* q_func() { return static_cast(q_ptr); } \
    inline const Class* q_func() const { return static_cast(q_ptr); } \
    friend class Class;
    
#define Q_Q(Class) Class * const q = q_func()

        同理,我们在私有类中,有时候需要调用主类的方法,这两个宏的作用就是为了可以在私有类中拿到主类的指针。我们在私有类的构造函数中传入主类指针,并赋值给q_ptr。因为这里是拿到主类的指针,并不存在智能指针的问题,所以此处并没有借助qGetPtrHelper()函数。      

    Qt采用了Pimpl机制,也就是私有实现,以QObject为基类的类的具体实现放在一个XXXPrivate中,头文件中通过宏Q_DECLARE_PRIVATE来返回将d_ptr转换为当前类的XXXPrivate的一个指针,然后cpp中通过宏Q_D来获取这个d指针。到这里你可能会有一个疑问了,QObject的子类的XXXPrivate的指针是如何传递给d_ptr的? 带着这个问题我们往下看:

        首先我们先看一个QPushButton的例子,将MainWindow设为QPushButton的parent:

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
{
    ui.setupUi(this);
    connect(ui.pushButton, &QPushButton::clicked,this,&MainWindow::onButtonClicked);
    QPushButton *button = new QPushButton(this);
}

我们依次查看构造函数看看发生了什么:
QPushButton,调用了QAbstractButton的一个protected权限的构造函数:

QPushButton::QPushButton(QWidget *parent)
    : QAbstractButton(*new QPushButtonPrivate, parent)
{
    Q_D(QPushButton);
    d->init();
}

QWidget,又调用了QObject的一个proteced权限的构造函数:

/*! \internal
*/
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;
    }
}

QObject 的proteced权限的构造函数,可以看到QPushButton构造函数中的*new QPushButtonPrivate,最终传递给了d_ptr。

/*!
    \internal
 */
QObject::QObject(QObjectPrivate &dd, QObject *parent)
    : d_ptr(&dd)
{
	...
}

用一张图表示:

qt d指针和对象树_第1张图片

        QWidget中并没有出现d_ptr指针,原来是从Qbject继承而来。QObject中我们新添加的那个protected构造函数传入一个QWidgetPrivate,用此给QObject中的d_ptr赋值,而这便是我们唯一的d_ptr。现在总算真正理解之前d_func()中那些类型转换的作用,就是保证我们可以拿到当前正确类型的private指针。

        那么同理,ObjectPrivate是继承于QObjectData,而在QObjectData中有着QObject *q_ptr;。 所有QObject子类的私有类,均继承于ObjectPrivate,故而子类中也不会出现q_ptr,在QObject的构造函数中,我们把this指针给其赋值,在通过使用Q_Q宏,我们同样可以拿到正确类型的主类q指针。

d指针作用:

        1 隐藏接口具体实现及细节

        2 提高程序编译速度

        3 最大程度实现二进制兼容

                3.1 二进制兼容动态库:在老版本下动态库运行新的引用程序,在不经过编译的情况下,能够在新版本的动态库下运行;在需要编译的情况下 ,不需要修改源码,我们就说动态库是兼容源代码的

                3.2 要想使一个dll能够达到二进制兼容,对于类中对象/结构/数据都应保存不变,如果在类中对成员对象等进行改动则会影响对象的数据模型,导致数据在数据模型中发生变化,程序使用新版本编译后会导致程序崩溃

                3.3 应对方法:为了使增加或添加项后部队数据模型的大小产生影响

               (1)预先分配若干个保留空间,添加项时使用保留空间的项(使用位作用域:int n:20 即该变量预先占用几个bit的空间) ----浪费空间

               (2)将预分配保留空间类型由“常规变量”-->"对象指针"

对象树 

        qt中以QObject基类的类的构造函数都会指定一个parent的参数,那么这个参数有什么用呢?比如上文中QPushButton,指定了this也就是MainWindow类为parent,在一次次调用父类构造函数时会调用到QWidget的一个构造函数

/*! \internal
*/
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;
    }
}

参数中parent也就是new QPushButton(this) 时指定为parent的this指针。接着会调用 d->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);
    if (allWidgets)
        allWidgets->insert(q);
	...
}

又出现一个Q_Q宏,经过类似的分析发现就是定义了一个q指针指向了d_ptr指针的q_ptr成员;allWidgets时QWidgetPrivate的一个静态成员,保存了所有的以QWidget为基类的指针。这个q指向的q_ptr在最终调用QObject构造函数时初始化的:

QObject::QObject(QObjectPrivate &dd, QObject *parent)
    : d_ptr(&dd)
{
    Q_D(QObject);
    d_ptr->q_ptr = this;
    ...
}

继续看 void QWidgetPrivate::init(QWidget *parentWidget, Qt::WindowFlags f) 这个函数:

void QWidgetPrivate::init(QWidget *parentWidget, Qt::WindowFlags f)
{
    ...
    else if (parentWidget)
        q->setParent(parentWidget, data.window_flags);
	...
}

调用的void QWidget::setParent(QWidget *parent, Qt::WindowFlags f)这个函数:

void QWidget::setParent(QWidget *parent, Qt::WindowFlags f)
{
	...
	d->setParent_sys(parent, f);
	...
}

调用的时void QWidgetPrivate::setParent_sys(QWidget *newparent, Qt::WindowFlags f) 这个函数:

void QWidgetPrivate::setParent_sys(QWidget *newparent, Qt::WindowFlags f)
{
	...
	QObjectPrivate::setParent_helper(newparent);
	...
}

调用的void QObjectPrivate::setParent_helper(QObject *o) 这个函数:

void QObjectPrivate::setParent_helper(QObject *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) {
                QChildEvent e(QEvent::ChildAdded, q);
                QCoreApplication::sendEvent(parent, &e);
            }
        }
    }
	...
}

        QPushButton设置parent最终传递给了d->parent;我们还可以看到parent->d_func()->children.append(q);这行代码其实是获取到了parent的d指针,然后把当前的指针保存到了parent的d指针的children的变量中。至此我们大概了解了d指针中的parent和children是如何赋值的。
  构造函数差不多了,来看下QObject的析构函数:

QObject::~QObject()
{
	...
	if (!d->children.isEmpty())
        d->deleteChildren();
	...
}
...
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;
}

    看一下children的数据结构:

class Q_CORE_EXPORT QObjectData {
public:
    virtual ~QObjectData() = 0;
    QObject *q_ptr;
    QObject *parent;
    QObjectList children;
......
};

typedef QList QObjectList;

  使用一个链表存储 孩子对象。

利用Qt 对象间的父子关系可以构成对象树

删除树中的节点时会导致对应的子树被销毁

qt d指针和对象树_第2张图片

测试:

#include 
#include 
void fcTest()
{
    QObject* p = new QObject();
    QObject* c1 = new QObject();
    QObject* c2 = new QObject();

    c1->setParent(p);
    c2->setParent(p);

    qDebug() << "c1 = " << c1;
    qDebug() << "c2 = " << c2;

    const QObjectList& list = p->children();            //链表对象


    for(int i = 0; i < list.length(); i++)
    {
        qDebug() << list[i];
    }
    qDebug() << "p: " << p;

    qDebug() << "c1 parent: " << c1->parent();
    qDebug() << "p2 parent: " << c2->parent();
}

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

    fcTest();

    return a.exec();
}

qt d指针和对象树_第3张图片

由上面的deleteChildren可以看出

当Qt 对象被销毁时

— 将自己从父对象的 Children List 移除

— 将自己的 Children List 中的所有对象销毁

什么是Qt 对象:(定义一个类,继承于 QObject ,因此而产生的对象才叫 Qt 对象)

 

delete需要注意的事项:

Qt内存泄漏总结(包括检测工具)_FlyWM_的博客-CSDN博客_qt内存泄漏检测

你可能感兴趣的:(qt,qt)