D\Q指针及QT优化Q_Q宏和Q_D宏

本文详细解析了Qt中Q_DECLARE_PRIVATE和Q_DECLARE_PUBLIC宏的作用,展示了如何通过D指针保证类的二进制兼容性,以及如何简化Q_D和Q_Q的使用。涉及了私有类、类结构变化对兼容性的影响和解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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是源码兼容的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值