Q_D macro and d pointer in Qt

_ZTS7QObject

1. The reference of Q_D in the document

The setting of Q_D is intended to obtain private class pointers conveniently, and the file is qglobal.h. The following ## is a hyphen defined by the macro. Assuming the class name is A, then A##Private translates to APrivate.

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

The d_func() function is implemented as follows:

 #define Q_DECLARE_PRIVATE(Class) \
     inline Class##Private* d_func() { return reinterpret_cast<Class##Private *>(qGetPtrHelper(d_ptr)); } \
     inline const Class##Private* d_func() const { return reinterpret_cast<const Class##Private *>(qGetPtrHelper(d_ptr)); } \
     friend class Class##Private;

Although the d_func() here is in the macro, if it is substituted into a specific type, it becomes a function with the private class pointer of Class##Private as the return value and func as the function name. Here qGetPtrHelper is

 template <typename T> static inline T *qGetPtrHelper(T *ptr) { return ptr; }

The T in this template function needs to be embedded in a specific class. The above Q_DECLARE_PRIVATE becomes a call of this template class. This template class converts this class pointer into a static one. With the above three pieces of code, if you want to declare a private class in a certain class A, just come to a Q_D(A), and then Q_DECLARE_PRIVATE(A).

Second, the expansion of macros and templates

Expanding part of the former macro and the latter macro, becomes

 #define Q_D(A) APrivate *const d= d_func()
 inline APrivate* d_func() { return reinterpret_cast<APrivate *>(qGetPtrHelper((d_ptr));}

The input value of the function qGetPtrHelper above is a member variable in the QObject class, the d_ptr pointer, which is defined as follows:

 QScopedPointer<QObjectData> d_ptr;

According to the template function call, what is returned is still a variable of type QScopedPointer, that is, a smart scoped pointer pointing to the type of QObjectData. Finally, call reinterpret_cast to reinterpret the previously obtained pointer and turn it into an APrivate type.

The definition of QObjectData here is also inside QObject

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

As for QScopedPointer, it is a bit complicated. The core idea is a pointer that does not need to be destroyed by itself.

3. Inheritance and calling

Write a class MyQFileSystemModel to inherit QFileSystemModel and use the Q_D macro in MyQFileSystemModel, an error will occur:

 C:\Qt\Qt5.9.2\5.9.2\mingw53_32\include\QtWidgets/qfilesystemmodel.h: In constructor 'MyQFileSystemModel::MyQFileSystemModel()':
  C:\Qt\Qt5.9.2\5.9.2\mingw53_32\include/QtCore/qglobal.h:1002:28: error: 'QFileSystemModelPrivate* QFileSystemModel::d_func()' is private
       inline Class##Private* d_func() { return reinterpret_cast<Class##Private *>(qGetPtrHelper(d_ptr)); } \
                              ^
  C:\Qt\Qt5.9.2\5.9.2\mingw53_32\include\QtWidgets/qfilesystemmodel.h:152:5: note: in expansion of macro 'Q_DECLARE_PRIVATE'
       Q_DECLARE_PRIVATE(QFileSystemModel)
       ^
  C:\Qt\Qt5.9.2\5.9.2\mingw53_32\include/QtCore/qglobal.h:1016:54: error: within this context
   #define Q_D(Class) Class##Private * const d = d_func()
                                                       ^
 ..\student\myqfilesystemmodel.cpp:5:5: note: in expansion of macro 'Q_D'
      Q_D(const QFileSystemModel);
      ^
 C:\Qt\Qt5.9.2\5.9.2\mingw53_32\include/QtCore/qglobal.h:1016:43: warning: unused variable 'd' [-Wunused-variable]
  #define Q_D(Class) Class##Private * const d = d_func()
                                            ^
 ..\student\myqfilesystemmodel.cpp:5:5: note: in expansion of macro 'Q_D'

It means that d_func() is a private function in the QFileSystemModel class. For private functions in C++, subclasses cannot inherit them.

Four, q pointer

After writing a private class MyQFileSystemModelPrivate of MyQFileSystemModel, you can use the Q_Q macro to reference the corresponding public class from the private class.

 #ifndef MYQFILESYSTEMMODEL_P_H
  #define MYQFILESYSTEMMODEL_P_H
  #include <myqfilesystemmodel.h>
  class MyQFileSystemModelPrivate;
  class MyQFileSystemModel;
  QT_BEGIN_NAMESPACE
  class MyQFileSystemModelPrivate: public QFileSystemModelPrivate
  {
  public:
     //MyQFileSystemModelPrivate(MyQFileSystemModel * parent):q_ptr(parent){}
 public:
     Q_DECLARE_PUBLIC(MyQFileSystemModel)
     MyQFileSystemModel *q_ptr;
 };
 QT_END_NAMESPACE
 #endif // MYQFILESYSTEMMODEL_P_H

What needs special attention here is that the line MyQFileSystemModel *q_ptr; is indispensable. C++’s static_cast is limited to the type being converted. If class B inherits class A, then converting from class B to class A is no problem at all. However, if you want to convert class A into class B, you need to be in class B, and the problem arises, class A may not be as full as class B, and the converted class may be disabled, so in general, this conversion is Can’t be successful. Unless: Class B contains a pointer to Class A. If the q_ptr pointer declaration is removed, this error will be reported:

 C:\Qt\Qt5.9.2\5.9.2\mingw53_32\include\QtCore\qglobal.h:1012: error: invalid static_cast from type 'QObject*' to type 'MyQFileSystemModel*'
      inline Class* q_func() { return static_cast<Class *>(q_ptr); } \
                                                                ^

Five, private class function call

The public classes and private classes in Qt are closely related, and the functions of private classes cannot be used directly. If you want to use it, you must call it through other classes. When Qt is compiled, QFileSystemModel.h is a bunch of declarations, which will export many functions to QtWidget.dll. It is worth noting that only the classes, functions and variables declared in this file are exported here, and the functions and variables declared in the private class header file QFileSystemModel_p.h will not be exported.

Of course, when compiling, the declarations and definitions in QFileSystemModelPrivate.h and QFileSystemModel.cpp will also be referenced. Under normal circumstances, we are only Qt users. Under the Windows system, we only need to download and install it, and we don’t need to compile it ourselves. But when we want to use private classes to do some in-depth customization, we hope to be able to call members of private classes directly. At this time, if only the .h file is included, an undefined reference error will be reported. In other words, the compilation can pass, the link cannot pass, and the private class members cannot be found.

The solution is to include the corresponding cpp files into the project directory, but new problems will arise at this time. The implementation of some classes has been compiled into the library function. At this time, it is re-implemented in the cpp file, and a warning will be reported: redeclared without dllimport attribute. Just delete the implementation function in the cpp file.

Although the use of private classes can be realized through the above method, it is worth noting that the private function with the same name in the compiled binary file dll is still working. Still take QFileSystemModel as an example, it has been compiled by Qt and placed in QWidget.dll. There will also be a function of QFileSystemModelPrivate in this dll file (but there will be no declaration directly pointing to the outside, and the outside cannot be directly linked and referenced), otherwise the private class is completely useless. When a new project needs to use QFileSystemModel, and QFileSystemModelPrivate is indirectly referenced, the version used is the finished product compiled before. When a new project wants to directly call a private class function, it is the new version that is used.

Use Dependency to open Qt5Widgets.dll to see that the function name of the private class is as follows:

 _ZN16QFileSystemModelC1ER23QFileSystemModelPrivateP7QObject

This may be just a constructor. There are only two functions of the above QFileSystemModelPrivate in Qt5Widgets.dll. It can be seen that most functions of private classes are not exported. The form of private member functions in QFileSystemModelPrivate.dll compiled directly by the method in this section is as follows:

 _ZNK23QFileSystemModelPrivate4nodeERK11QModelIndex

The representative formal parameter is the private class function node of QModelIndex, and there are many functions like this, which can correspond to the .h file of the private class.