在使用Qt的过程都会听到或遇到神秘的d指针(Q_D)、q指针(Q_Q),接下来通过剖析Qt源,弄清楚他们工作原理
一、Pimpl机制
- 简介
// objclass.h
class ClassImpl;
class ObjClass
{
public:
ObjClass();
~ObjClass();
private:
ClassImpl* m_dateImpl;
};
// classimpl.h
class ClassImpl
{
public:
ClassImpl();
~ClassImpl();
// 具体细节实现
void fun();
};
Pimpl的思想是把类的公有使用接口与所有关于类的私有化实现分离。由于其他类使用该类的接口需要依赖类的头文件,头文件中任何变化都会影响到其他类,即使仅仅是对private或protected进行修改。Pimpl机制可以用来减少因private(protected)修改造成重新编译的代价。
二、d指针(Q_D)
以QFocusFrame为例:
在qt中我们常常看到如上图Q_D(QFocusFrame)的使用
在qgloba.h中可以看到宏Q_D的定义如下:
#define Q_D(Class) Class##Private * const d = d_func()
调用d_func()函数返回类型为Class##Private的d指针变量。即在上例中则为:调用QFocusFrame的d_func()方法,返回类型为QFocusFramePrivate,d指针变量。
在该类中除了类方法外,没有看到任何的成员属性,但是发现在private有一个特殊的宏Q_DECLARE_PRIVATE。
在qglobal.h中可以看到宏Q_DECLARE_PRIVATE的定义如下:
#define Q_DECLARE_PRIVATE(Class) \
inline Class##Private* d_func() \
{ Q_CAST_IGNORE_ALIGN(return reinterpret_cast<Class##Private *>(qGetPtrHelper(d_ptr));) } \
inline const Class##Private* d_func() const \
{ Q_CAST_IGNORE_ALIGN(return reinterpret_cast<const Class##Private *>(qGetPtrHelper(d_ptr));) } \
friend class Class##Private;
此宏展开后,其实就是在类中定义了一个返回值为Class##Private*,函数名为d_func的内联函数。d_func函数调用qGetPtrHelper(d_ptr)并将返回值转为Class##Private*。
在qglobal.h中可以看到qGetPtrHelper函数定义如下:
template <typename T> inline T *qGetPtrHelper(T *ptr) { return ptr; }
template <typename Ptr> inline auto qGetPtrHelper(Ptr &ptr) -> decltype(ptr.operator->()) { return ptr.operator->(); }
通过模板定义传入ptr返回ptr的一个函数。那么在上面传入d_ptr参数。d_ptr又是在什么地方定义,什么地方赋值的呢?似乎在QFocusFrame类中我们没有发现。
在该例中,QFocusFrame有以下继承关系:
在QObject类protected中定义了d_ptr,类型为:QScopedPointer< QObjectData >,因此QFocusFrame便继承了基类的d_ptr成员属性。
同时QObject类protected声明QObject(QObjectPrivate &dd, QObject *parent)构造方法,在qobject.cpp里面通过初始化列表的方式对d_ptr进行了赋值。
在QFocusFrame构造函数中调用QWidget的protected构造函数,并传入QFocusFramePrivate的实例,QWidget构造函数调用QObject的构造函数对d_ptr赋值。
到此d指针的定义和赋值就清楚了,d指针的类型为QObjectData,而在QFocusFrame构造函数中传入参数却为:QFocusFramePrivate,这又是为何?还有QFocusFramePrivate内部又做了什么呢?
从该类图中可以看出,QFocusFramePrivate为QObjectData的子类,因此在QFocusFrame构造时便可传入QFocusFramePrivate对d_ptr初始化。
QFocusFramePrivate内部定义了QFocusFrame全部的成员属性,通过此方法将类的所有私有成员变量封装到一个新类统一管理。
三、q指针(Q_Q)
在QFocusFramePrivate通过Q_Q拿到q指针,通过q指针访问QFocusFrame
在qglobal.h中可以看到Q_Q的定义如下:
#define Q_Q(Class) Class * const q = q_func()
调用q_func()函数返回类型为Class的q指针变量。即在上例中则为:调用QFocusFramePrivate的q_func()方法,返回类型为QFocusFrame,q指针变量。
同样,在QFocusFramePrivate中存在宏Q_DECLARE_PUBLIC
在qglobal.h中可以看到Q_DECLARE_PUBLIC的定义如下:
#define Q_DECLARE_PUBLIC(Class) \
inline Class* q_func() { return static_cast<Class *>(q_ptr); } \
inline const Class* q_func() const { return static_cast<const Class *>(q_ptr); } \
friend class Class;
到此,没有看见q_ptr的定义和赋值的地方,那么p_ptr的定义又在何处?赋值又是什么时候呢?
在QObjectData中声明了p_ptr,类型为QObject
通过类关系可知,QFocusFramePrivate中继承了QObjectData的所有属性
在QObject的构造函数中我们可以看到,将q_ptr赋值为this。即QFocusFrame在构造通过传入QFocusFramePrivate的实例对象,对d指针进行了初始化,同时对QFocusFramePrivate内的q_ptr初始化为this(QFocusFrame)
四、Demo
// widget.h
#ifndef WIDGET_H
#define WIDGET_H
class ObjData;
class Obj
{
protected:
Obj(ObjData* ptr): d_ptr(ptr) {}
ObjData* d_ptr = nullptr;
};
class Widget : public Obj
{
public:
Widget(int w, int h);
void print();
};
#endif // WIDGET_H
在widgeh.h中前置声明ObjData,在Obj类中定义d_ptr指针,并且提供初始化d_ptr的构造函数。Widget继承Obj,在Widget内部便有了d_ptr指针,同时在Widget中,可以调用Obj的构造函数进行初始化。
// widget.cpp
#include "widget.h"
#include <typeinfo>
#include <iostream>
class ObjData
{
public:
virtual ~ObjData() {}
Obj* q_ptr = nullptr;
};
class WidgetPrivate : public ObjData
{
public:
WidgetPrivate(int w, int h): m_w(w), m_h(h) {}
virtual ~WidgetPrivate() {}
int width() const
{
return m_w;
}
int height() const
{
return m_h;
}
private:
int m_w;
int m_h;
};
Widget::Widget(int w, int h)
: Obj(new WidgetPrivate(w, h))
{
d_ptr->q_ptr = this;
}
void Widget::print()
{
auto d = dynamic_cast<WidgetPrivate*>(d_ptr);
std::cout << "W=" << d->width() << " H=" << d->height() << std::endl;
}
widget.cpp中定义ObjData,将Widget的具体实现和Widget的接口分离。对Widget细节的实现放在WidgetPrivate中,因此对Widget实现细节的修改并不会影响widget.h的改动,当有其他工程包含widget.h时,也不会导致该工程的编译。