Qt中的Q_D宏和d指针
1 _ZTS7QObject
一、Q_D的在文件中的提法
Q_D的设置意在方便地获取私有类指针,文件为qglobal.h。下面的##是宏定义的连字符。假设类名是A,那么A##Private翻译过来就是APrivate。
1 #define Q_D(Class) Class##Private * const d = d_func()
d_func()函数如下实现:
1 #define Q_DECLARE_PRIVATE(Class) \
2 inline Class##Private* d_func() { return reinterpret_cast<Class##Private *>(qGetPtrHelper(d_ptr)); } \
3 inline const Class##Private* d_func() const { return reinterpret_cast<const Class##Private *>(qGetPtrHelper(d_ptr)); } \
4 friend class Class##Private;
这里的d_func()虽然在宏里面,但是如果代入具体的类型,这里面就变成了以Class##Private的私有类指针为返回值,以func为函数名的函数。这里的qGetPtrHelper是
1 template <typename T> static inline T *qGetPtrHelper(T *ptr) { return ptr; }
这个模板函数里面里的T要套入某个具体的类。上面的那个Q_DECLARE_PRIVATE里面的就成了这个模板类的一个调用。这个模板类就是把这个类指针转换成了静态的。有了上面的这三段代码,如果想在某个类A里面声明一个私有类,直接来一个Q_D(A),再Q_DECLARE_PRIVATE(A)就可以了。
CSDN QT开发技术文章推荐:Qt开发必备技术栈学习路线和资料
二、宏和模板的展开
展开前一个宏和后一个宏的一部分,成了
1 #define Q_D(A) APrivate *const d= d_func()
1 inline APrivate* d_func() { return reinterpret_cast<APrivate *>(qGetPtrHelper((d_ptr));}
上面这个函数qGetPtrHelper调用的输入值是QObject类里面的一个成员变量,d_ptr指针,定义如下:
1 QScopedPointer<QObjectData> d_ptr;
根据模板函数调用返回的仍然是一个QScopedPointer<QObjectData>类型的变量,也就是一个指向QObjectData类型的智能限域指针。最后调用reinterpret_cast重新解释前面得到的指针,把它变成指向APrivate类型的。
这里的QObjectData的定义也在QObject里面
1 class Q_CORE_EXPORT QObjectData {
2 public:
3 virtual ~QObjectData() = 0;
4 QObject *q_ptr;
5 QObject *parent;
6 QObjectList children;
7
8 uint isWidget : 1;
9 uint blockSig : 1;
10 uint wasDeleted : 1;
11 uint isDeletingChildren : 1;
12 uint sendChildEvents : 1;
13 uint receiveChildEvents : 1;
14 uint isWindow : 1; //for QWindow
15 uint unused : 25;
16 int postedEvents;
17 QDynamicMetaObjectData *metaObject;
18 QMetaObject *dynamicMetaObject() const;
19 };
至于QScopedPointer,有点复杂,核心思想是一个不需要自己销毁的指针。
三、继承和调用
自己写一个类MyQFileSystemModel继承QFileSystemModel,在MyQFileSystemModel中使用Q_D宏,会出现错误:
1 C:\Qt\Qt5.9.2\5.9.2\mingw53_32\include\QtWidgets/qfilesystemmodel.h: In constructor 'MyQFileSystemModel::MyQFileSystemModel()':
2 C:\Qt\Qt5.9.2\5.9.2\mingw53_32\include/QtCore/qglobal.h:1002:28: error: 'QFileSystemModelPrivate* QFileSystemModel::d_func()' is private
3 inline Class##Private* d_func() { return reinterpret_cast<Class##Private *>(qGetPtrHelper(d_ptr)); } \
4 ^
5 C:\Qt\Qt5.9.2\5.9.2\mingw53_32\include\QtWidgets/qfilesystemmodel.h:152:5: note: in expansion of macro 'Q_DECLARE_PRIVATE'
6 Q_DECLARE_PRIVATE(QFileSystemModel)
7 ^
8 C:\Qt\Qt5.9.2\5.9.2\mingw53_32\include/QtCore/qglobal.h:1016:54: error: within this context
9 #define Q_D(Class) Class##Private * const d = d_func()
10 ^
11 ..\student\myqfilesystemmodel.cpp:5:5: note: in expansion of macro 'Q_D'
12 Q_D(const QFileSystemModel);
13 ^
14 C:\Qt\Qt5.9.2\5.9.2\mingw53_32\include/QtCore/qglobal.h:1016:43: warning: unused variable 'd' [-Wunused-variable]
15 #define Q_D(Class) Class##Private * const d = d_func()
16 ^
17 ..\student\myqfilesystemmodel.cpp:5:5: note: in expansion of macro 'Q_D'
意思是,d_func()是QFileSystemModel这个类里面的私有函数,对于c++的私有函数,子类是不能够继承的。
四、q指针
写一个MyQFileSystemModel的私有类MyQFileSystemModelPrivate,就可以使用Q_Q宏,从私有类引用对应的公有类了。
1 #ifndef MYQFILESYSTEMMODEL_P_H
2 #define MYQFILESYSTEMMODEL_P_H
3 #include <myqfilesystemmodel.h>
4 class MyQFileSystemModelPrivate;
5 class MyQFileSystemModel;
6 QT_BEGIN_NAMESPACE
7 class MyQFileSystemModelPrivate: public QFileSystemModelPrivate
8 {
9 public:
10 //MyQFileSystemModelPrivate(MyQFileSystemModel * parent):q_ptr(parent){}
11 public:
12 Q_DECLARE_PUBLIC(MyQFileSystemModel)
13 MyQFileSystemModel *q_ptr;
14 };
15 QT_END_NAMESPACE
16 #endif // MYQFILESYSTEMMODEL_P_H
这里面需要尤其注意的是,MyQFileSystemModel *q_ptr;这一行是不能少的。c++的static_cast是对被转换的类型有限制的。如果B类继承了A类,那么从B类转换成A类是完全没有问题的。但是,如果想把A类转换成B类,就要求B类里面,问题就出现了,A类可能没有B类那么丰满,转换出来的类可能是残废的,所以一般情况下,这种转换是不能成功的。除非:B类里面包含了一个指向A类的指针。如果把q_ptr指针声明去掉了,会报这个错误:
1 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*'
2 inline Class* q_func() { return static_cast<Class *>(q_ptr); } \
3 ^
五、私有类函数调用
Qt中的公有类和私有类关系密切,私有类的函数是不能够直接使用的。要是想用,一定要通过其他类调用。在Qt编译的时候,QFileSystemModel.h是一堆声明,会导出很多函数到QtWidget.dll里面去。值得注意的是,这里导出的只是这个文件里所声明的类、函数和变量,而不会导出只在私有类头文件QFileSystemModel_p.h里面声明过的函数和变量。
当然,编译的时候,也会引用QFileSystemModelPrivate.h和QFileSystemModel.cpp里的声明以及定义。一般情况下,我们只是Qt的使用者,在windows系统下,只要下载安装就好,不需要自己再编译了。但是当我们想使用私有类做一些更深入的定制的时候,希望能够调直接调用私有类的成员。这时候,如果只是include了.h文件,就会报undefined reference错误。也就是说,编译能过,链接过不了,找不到私有类成员。
解决的方法就是将对应的cpp文件也包含进工程目录里面,但这时候会有新的问题出现。有些类的实现已经编译到库函数里面了,这时候又在cpp文件重新实现,会报警告:redeclared without dllimport attribute。只需要把cpp文件中的实现函数删除掉就可以了。
虽然通过上面的方法,可以实现私有类的使用,但是值得注意的是,已经编译好的二进制文件dll里面的同名私有函数还在起着作用。还是用QFileSystemModel为例,它已经被Qt编译好放到了QWidget.dll里面了。在此dll文件里也会有QFileSystemModelPrivate的函数(只是不会有直接指向外部的声明,外部不能直接链接引用而已),否则私有类就完全没用了。当新的工程要使用到QFileSystemModel,而QFileSystemModelPrivate被间接地引用,使用的版本就是之前编译好的成品。当新的工程要直接调用私有类函数的时候,才是用的新的版本。
用Dependency打开Qt5Widgets.dll看到私有类的函数名形式如下:
1 _ZN16QFileSystemModelC1ER23QFileSystemModelPrivateP7QObject
这可能只是一个构造函数,在Qt5Widgets.dll里只有两个上面这种QFileSystemModelPrivate的函数,可见大多数私有类的函数没有导出。而直接用本节方法编译出来的QFileSystemModelPrivate.dll里面的私有成员函数形式如下:
1 _ZNK23QFileSystemModelPrivate4nodeERK11QModelIndex
代表形参是QModelIndex的私有类函数node,像这样的函数还有很多,它们与私有类的.h文件能够对应起来。
这里的QObjectData的定义也在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;
};
至于QScopedPointer,有点复杂,核心思想是一个不需要自己销毁的指针。
三、继承和调用
自己写一个类MyQFileSystemModel继承QFileSystemModel,在MyQFileSystemModel中使用Q_D宏,会出现错误:
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'
意思是,d_func()是QFileSystemModel这个类里面的私有函数,对于c++的私有函数,子类是不能够继承的。
CSDN QT开发技术文章推荐:Qt开发必备技术栈学习路线和资料
四、q指针
写一个MyQFileSystemModel的私有类MyQFileSystemModelPrivate,就可以使用Q_Q宏,从私有类引用对应的公有类了。
#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
这里面需要尤其注意的是,MyQFileSystemModel *q_ptr;这一行是不能少的。c++的static_cast是对被转换的类型有限制的。如果B类继承了A类,那么从B类转换成A类是完全没有问题的。但是,如果想把A类转换成B类,就要求B类里面,问题就出现了,A类可能没有B类那么丰满,转换出来的类可能是残废的,所以一般情况下,这种转换是不能成功的。除非:B类里面包含了一个指向A类的指针。如果把q_ptr指针声明去掉了,会报这个错误:
1 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*'
2 inline Class* q_func() { return static_cast<Class *>(q_ptr); } \
3 ^
五、私有类函数调用
Qt中的公有类和私有类关系密切,私有类的函数是不能够直接使用的。要是想用,一定要通过其他类调用。在Qt编译的时候,QFileSystemModel.h是一堆声明,会导出很多函数到QtWidget.dll里面去。值得注意的是,这里导出的只是这个文件里所声明的类、函数和变量,而不会导出只在私有类头文件QFileSystemModel_p.h里面声明过的函数和变量。
当然,编译的时候,也会引用QFileSystemModelPrivate.h和QFileSystemModel.cpp里的声明以及定义。一般情况下,我们只是Qt的使用者,在windows系统下,只要下载安装就好,不需要自己再编译了。但是当我们想使用私有类做一些更深入的定制的时候,希望能够调直接调用私有类的成员。这时候,如果只是include了.h文件,就会报undefined reference错误。也就是说,编译能过,链接过不了,找不到私有类成员。
解决的方法就是将对应的cpp文件也包含进工程目录里面,但这时候会有新的问题出现。有些类的实现已经编译到库函数里面了,这时候又在cpp文件重新实现,会报警告:redeclared without dllimport attribute。只需要把cpp文件中的实现函数删除掉就可以了。
虽然通过上面的方法,可以实现私有类的使用,但是值得注意的是,已经编译好的二进制文件dll里面的同名私有函数还在起着作用。还是用QFileSystemModel为例,它已经被Qt编译好放到了QWidget.dll里面了。在此dll文件里也会有QFileSystemModelPrivate的函数(只是不会有直接指向外部的声明,外部不能直接链接引用而已),否则私有类就完全没用了。当新的工程要使用到QFileSystemModel,而QFileSystemModelPrivate被间接地引用,使用的版本就是之前编译好的成品。当新的工程要直接调用私有类函数的时候,才是用的新的版本。
用Dependency打开Qt5Widgets.dll看到私有类的函数名形式如下:
_ZN16QFileSystemModelC1ER23QFileSystemModelPrivateP7QObject
这可能只是一个构造函数,在Qt5Widgets.dll里只有两个上面这种QFileSystemModelPrivate的函数,可见大多数私有类的函数没有导出。而直接用本节方法编译出来的QFileSystemModelPrivate.dll里面的私有成员函数形式如下:
1 _ZNK23QFileSystemModelPrivate4nodeERK11QModelIndex
代表形参是QModelIndex的私有类函数node,像这样的函数还有很多,它们与私有类的.h文件能够对应起来。
本文福利, 免费领取Qt开发学习资料包、技术视频,内容包括(C++语言基础,Qt编程入门,QT信号与槽机制,QT图像绘制,QT网络,QT数据库编程,QT项目实战,QT嵌入式开发,Quick模块等等)↓↓↓↓↓↓见下面↓↓文章底部点击免费领取↓↓