二进制兼容
什么是二进制兼容?
简单理解就是升级了QT的dll文件,不需要重新编译项目就能正常运行。
为什么要保证二进制兼容?
因为违反二进制兼容的行为容易出错,UB(未定义的行为),甚至奔溃。
什么行为会破坏二进制兼容?
- 添加或删除非静态变量
- 改变非静态变量的顺序
- 更改成员的类型(但签名除外)
什么行为不会破坏二进制兼容?
- 添加非虚函数到类型(包括信号、槽、构造函数)
- 添加新的静态数据成员
d指针
什么是d指针?
d指针一般是接口类文件里指向实现类的指针。
d指针是不透明指针的一种实现,也可以说d指针是一种设计模式(桥接模式)。
什么是不透明指针?
不透明指针可以隐藏实现细节,当实现改变后不需要重新编译就能使用。程序员可将接口/接口类写的很简洁,而降大部分的实现放在另一个文件中,这也保证了二进制兼容。
什么是桥接模式?
将抽象部分(业务功能)与实现部分(平台实现)分离,使他们都可以独立的改变。
d指针的优点
- 隐藏类的实现细节
- 添加新的数据成员到私有类中不会影响二进制兼容性
- 头文件只需要包含接口类需要的头文件,不需要包含私有实现类的头文件(编译速度会提升)
从QT源码看d指针
在QT中QObjectData是所有Private类的基类。
通过向上传递d_ptr,避免继承层次中多次分配xxxPrivate类。例如:在QLabel构造时,QLabelPrivate一直向上传递,直到传给QObject。
Q_DECLARE_PRIVATE是接口类调用的,声明私有类指针。
Q_DECLARE_PUBLIC是实现类调用的,声明公共类指针。
使用Q_D宏获取到的d指针是实现类的指针,它可以直接访问和操作私有数据。
使用Q_Q宏获取到的q指针是接口类的指针,它只能访问接口类的公有成员函数和公有成员变量。无法直接访问接口类的私有成员函数和私有成员变量。
Q_Q() 和 Q_D() 优点:提高代码复用性和可维护性。
class MyClassPrivate; // 声明私有类
class MyClass : public QObject
{
Q_OBJECT
Q_DECLARE_PRIVATE(MyClass) // 声明私有类指针(使用 Q_DECLARE_PRIVATE 宏将 MyClass 类的私有类指针声明为 MyClassPrivate)
public:
explicit MyClass(QObject *parent = nullptr);
~MyClass();
void publicFun();
private:
void anOtherFun();
private:
QScopedPointer<MyClassPrivate> d_ptr; // 私有类指针
};
// Q_D使用
void MyClass::publicFunction()
{
Q_D(MyClass); // 使用 Q_D() 宏声明并初始化 d 指针
// 在此可以使用 d 指针来访问实现类的私有成员和成员函数。
d->anOtherPriFun();
}
class MyClassPrivate {
Q_DECLARE_PUBLIC(MyClass) // 声明公共类指针
public:
explicit MyClassPrivate(MyClass *q);
void privateFun();
private:
void anOtherPriFun();
private:
MyClass *const q_ptr; // 公共类指针
};
// Q_Q使用
void MyClassPrivate::anOtherPriFun()
{
Q_Q(MyClass); // 使用 Q_Q() 宏声明并初始化 q 指针
// 在此可以使用 q 指针来访问接口类的公有成员和成员函数,不能访问私有成员。
q->publicFun();
}
RAII
构造时初始化资源,析构时释放资源,是C++为了解决异常安全提出的思想。
C++标准保证了即使抛出异常也能调用栈上对象的析构函数。
QScopedPointer的思想是RAII,作用是抛出异常时也能正确释放资源。
参考文档:
qt-wiki
https://wiki.qt.io/D-Pointer