文章目录
D\Q指针
PIMPL(Private Implementation)设计模式
用于隐藏类实现细节的设计模式,通过将私有成员封装在独立的私有类中,并通过指针间接访问,从而减少头文件依赖,提升编译速度和二进制兼容性。
头文件分离:公有类仅声明接口和指向私有类的指针(即D指针),私有类在源文件中定义,实现细节完全隐藏。
减少编译依赖:修改私有成员时,仅需重新编译实现文件,无需重新编译依赖该类的其他代码。
D指针(d_ptr)
存在于公有类中,指向私有实现类。
通常命名为XXXPrivate。
作用
将公有接口与私有实现分离,隐藏内部细节。
完全隔离实现细节,减少头文件依赖。
eg:
QObject的子类通过d_ptr访问其私有数据,如QLabel的布局和属性。
内存管理
通常通过QScopedPointer自动释放。
Q指针(q_ptr)
存在于私有类中,指向公有类对象。
作用
允许私有类通过公有类接口反向调用方法或访问公共成员。
示例:
QLabelPrivate中的q_ptr可以调用QLabel的公共方法setText()。
内存管理
无需管理,仅作为反向指针存在。
Q_DECLARE_PRIVATE()与Q_DECLARE_PUBLIC()
Q_D()与Q_Q()
https://blog.youkuaiyun.com/zhu_xz/article/details/6035861
https://blog.youkuaiyun.com/mznewfacer/article/details/6976293
https://blog.youkuaiyun.com/haoxinhaoxin/article/details/79473930
内部实现:https://blog.youkuaiyun.com/u010155023/article/details/50826102
宏定义在 QtGlobal(即qglobal.h)头文件中:
#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;
#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;
核心
-
在 QtServiceController 中通过 d_func() 可以获得 QtServiceControllerPrivate 的指针 d_ptr
-
在 QtServiceControllerPrivate 中通过 q_func() 可以获得 QtServiceController 的指针 q_ptr
d指针
二进制兼容性 binary compatible
https://community.kde.org/Policies/Binary_Compatibility_Issues_With_C%2B%2B
- 如果应用程序在动态链接某library的前后两个版本时不需要- 重新编译,则称这个library是二进制兼容的。
1. 不使用d指针
类声明:
class MyClass
{
public:
MyClass();
~MyClass();
private:
int MyClassVar;
};
class MyClassC: public MyClass
{
public:
MyClassC();
~MyClassC();
private:
int MyClassCVar;
};
- 我们编译该类,并且将其发布为Lib 1.0。对于Lib 1.1版本,我们希望加入新的数据成员。
- 经过上述改变后,我们发现工程可以通过编译,但是当运行调用Lib1.0时,程序崩溃。
- 这是由于当编译器在编译程序时,它是用offsets来标记在类中的成员变量。
- 而在加入成员变量后,改变了类的对象布局。MyClassC中MyClassCVar发生偏移。
- 在编译时,编译器为对象按照其大小在内存上分配了空间。而在运行时,由于新数据成员的加入导致类的构造函数重写了已经存在的内存空间,导致了程序崩溃。
=》所以只要版本已发布,除非重新编译工程,否则就不能更改类的结构和大小。那么,为了能够为原有类方便的引入新的功能,这就是Qt引入D指针的目的。
2. d指针
私有成员myVar是保证代码二进制兼容性的大敌,使用d指针改写。
2.1 添加一个私有类XXXPrivate类来定义原来类中的成员变量(文件命名方式:XXX_p.h,或写入.cpp文件中)
class MyClassPrivate
{
public:
MyClassPrivate(MyClass *parent);
private:
int MyClassVar;
};
2.2 修改原始xxx.h文件,引入Private类
a. 引用private类:使用前向声明class xxxPrivate缩短编译时间(不使用include )。
b. 定义指针d_ptr
指向私有实现类:使用const保证只被初始化一次。
c. 添加Q_DECLARE_PRIVATE
宏。
class MyClassPrivate; // 引入private类
class MyClass
{
public:
MyClass();
~MyClass();
protect:
MyClassPrivate * const d_ptr; // 定义了一个指针d_ptr指向私有实现类
private:
Q_DECLARE_PRIVATE(MyClass); //定义一些辅助函数和声明友元类
};
在XXXPrivate类中添加新的成员变量,XXX类只有一个指向XXXPrivate的D指针,这样就不会改变子类XXXC类的内存布局,就可以保证test.dll的二进制兼容性了。
作用
为原有类方便的引入新的功能(增加数据成员)。
增加类的信息封装。看不到具体数据类型,必须使用 getter 和 setter 去访问。
3. Q指针: 对父类或者公有类方法的访问
a. 通过构造函数将主类 MyObject 的指针传给 q_ptr
b. 加入宏 Q_DECLARE_PUBLIC
:
c. 通过XXXPrivate类中的q_str调用父类的protect或public方法。
class MyClassPrivate
{
public:
MyClassPrivate(MyClass *parent);
private:
MyClass * const q_ptr; // q_ptr指针就是指向公开的接口
Q_DECLARE_PUBLIC(MyClass); // 定义了辅助函数并声明了友元类
int MyClassVar;
};
=》形成构造函数:
MyClassPrivate::MyClassPrivate(MyClass *parent) : q_ptr(parent) { // MyClassPrivate.cpp
...
}
MyClass::MyClass():d_ptr(new MyClassPrivate(this)) { //MyClass.cpp
// 使用自己创建的Private 指针对d_ptr初始化
}
MyClassCPrivate::MyClassCPrivate(MyClassC *parent) : q_str(parent) { //MyClassPrivateC.cpp
...
}
MyClassC::MyClassC():d_ptr(new MyClassCPrivate(this)) { //MyClassC.cpp
}
4. 进一步简化:Q_D \ Q_Q
子类创建一个实例后,会额外创建继承路径上所有private类,造成空间浪费
优化后子类创建一个实例后,只会创建自身对应的private类对象和根private类对象
4.1 优化步骤
a. 所有private类都继承根private类;
b. 去掉各子类中d_ptr,均使用根private类的对象作为隐式D指针;
c. 去掉各子类中q_str,均使用根private类的Q指针指向根类(仅保留根private类的q_str);
d. 根类加入protect构造函数,如XXX(XXXPrivate &d),允许子类传入自己的private类来初始化。
//MyClass_p.h
class MyClassPrivate {
public:
MyClassPrivate();
MyClassPrivate(MyClass *parent);
private:
double MyClassVar;
public:
MyClass *q_ptr; // 3. 留
};
//MyClassC_p.h
class MyClassCPrivate : public MyClassPrivate { // 1
public:
MyClassCPrivate();
private:
double MyClassCVar;
// MyClassCPrivate * const q_ptr; // 3.
};
//MyClass.h
class MyClassPrivate;
class TESTDLL_API MyClass {
public:
MyClass(void);
protected: // 4.
//只有子类会访问以下构造函数
MyClass(MyClassPrivate &d); // 允许子类通过它们自己的private类来初始化
MyClassPrivate *d_ptr; // 2. 保留
};
//MyClassC.h
class MyClassCPrivate;
class TESTDLL_API MyClassC : public MyClass {
public:
MyClassC(void);
protected:
MyClassC(MyClassCPrivate &d);
private:
// MyClassPrivate * const d_ptr; // 2.
};
4.2
在使用q_str\d_str的时候要加上强制类型转换:
#define QPTR(Class) Class *q = static_cast<Class *>(q_ptr) //q_str实际指向private对应的类的对象
#define DPTR(Class) Class##Private *d = static_cast<Class##Private *>(d_ptr) //d_str实际指向类对应的private对象
=》类似于QT中经常见到的Q_Q宏和Q_D宏
5. 完整例子
// myclass.h
#ifndef MYCLASS_H
#define MYCLASS_H
#include <QtCore/QObject>
class MyClassPrivate;
class MyClass: public QObject
{
Q_OBJECT
public:
MyClass(QObject *parent = 0);
virtual ~MyClass();
void dummyFunc();
signal:
void dummySignal();
private:
MyClassPrivate * const d_ptr;
Q_DECLARE_PRIVATE(MyClass);
Q_DISABLE_COPY(MyClass);
};
#endif // MYCLASS_H
// myclass.cpp
#include "myclass.h"
class MyClassPrivate
{
public:
MyClassPrivate(MyClass *parent)
: q_ptr(parent)
{
}
void foobar()
{
Q_Q(MyClass);
emit q->dummySignal();
}
private:
MyClass * const q_ptr;
Q_DECLARE_PUBLIC(MyClass);
};
MyClass::MyClass(QObject *parent)
: QObject(parent)
, d_ptr(new MyClassPrivate(this))
{
}
MyClass::~MyClass()
{
Q_D(MyClass);
delete d;
}
void MyClass::dummyFunc()
{
Q_D(MyClass);
d->foobar();
}
PS:
- QT中的类常用一个XXXPrivate类对象来处理内部逻辑,使得内部逻辑和外部接口分开,这个XXXPrivate对象通过D指针来访问;在PrivateXXX中又需要引用Owner的内容,通过P指针来访问。
- 由于D和P指针是从基类继承下来的,子类中由于继承导致类型发生变化,需要通过static_cast类型转换,所以DPTR() 和 QPTR()宏定义实现了转换。
Q_DECLARE_PRIVATE
#define Q_DECLARE_PRIVATE(Class)
inline Class##Private* d_func() { return reinterpret_cast(qGetPtrHelper(d_ptr)); } // d_ptr 指针
inline const Class##Private* d_func() const { return reinterpret_cast(qGetPtrHelper(d_ptr)); } // const 指针
friend class Class##Private; // Private 类声明为MyClass的friend: 可以在 MyClass 这个类里面使用 Q_D(MyClass) 以及 Q_D(const MyClass)
Q_Q宏、Q_D宏
#define Q_D(Class) Class##Private * const d = d_func() // 封装从d_func()获取XXXPrivate指针的代码,使用 getter 函数获取
#define Q_Q(Class) Class * const q = q_func()
源码兼容Source compatible
二进制兼容稍弱一些,即除了链接某library的前后两个版本时需要重新编译之外,再不需要其他任何改动,则称这个library是源码兼容的。