一、隐式数据共享
隐式数据共享又称为回写复制,是QT框架中用于优化内存使用和提升性能的终于技术。其核心思想是:多个对象可以共享同一份数据,只有在某个对象需要修改数据时,才会真正复制数据,避免不必要的内存拷贝。
1、为什么需要隐式数据共享
在传统编程中,值类型(如 int、string)和容器(如数组、列表)的赋值操作往往伴随完整的数据拷贝。例如,当我们将一个包含 100 万个元素的列表赋值给另一个变量时,默认会复制所有元素,这不仅消耗大量内存,还会显著降低程序运行效率。引用类型(如指针)虽然避免了拷贝,但需要手动管理内存,容易引发悬空指针、内存泄漏等问题。
Qt 的隐式数据共享则有效的解决了上面的问题。
2、工作原理
每个支持隐式共享的 Qt 数据对象(如 QString、QList、QVector 等)内部包含两部分:
- 数据指针:指向实际存储数据的内存块。
- 引用计数:记录当前有多少个对象共享该数据块。
Qt在创建共享对象时,会将引用计数设置为 1。每当新对象引用共享数据时,引用计数就会增加,当对象取消引用共享数据时,引用计数会减少。当引用计数变为零时,共享数据将被删除。
当多个对象指向同一共享数据时,它们共享一块内存,只有引用计数递增,当其中一个对象需要修改数据时,会检查引用计数。若引用计数为1,也就是只有当前对象使用该数据,就直接进行修改,若引用计数大于1,也就是存在其他对象共享内存,先复制一份数据,将引用数量重置为1,再修改数据。
3、底层实现原理
Qt的隐式共享主要基于两个核心类:
- QSharedData:是所有共享数据类的基类,它内部包含一个原子引用计数器(
QAtomicInt ref),用于跟踪数据块的共享数量。自定义共享类型时,需继承QSharedData并在子类中定义实际数据成员。
class ShapeData : public QSharedData {
public:
// 构造函数:初始化数据,引用计数由QSharedData默认初始化为1
ShapeData() : x(0), y(0), color("black") {}
// 拷贝构造函数:深拷贝数据(用于写时复制)
ShapeData(const ShapeData& other)
: QSharedData(other), // 继承父类的引用计数状态(但实际复制时会重置)
x(other.x), y(other.y), color(other.color) {
}
~ShapeData() {} // 无需手动管理计数,由QSharedDataPointer处理
// 实际数据成员
int x;
int y;
QString color;
};
注意:
QSharedData的拷贝构造函数仅复制数据,不复制引用计数 —— 引用计数的管理由QSharedDataPointer负责。
- QSharedDataPointer<T>:是一个模板智能指针类,负责管理
QSharedData子类对象的生命周期,核心功能包括自动维护引用计数、触发写时复制。
template <typename T>
class QSharedDataPointer {
private:
T* d; // 指向共享数据(QSharedData子类实例)
public:
// 构造函数:创建新数据,计数初始化为1
QSharedDataPointer() : d(new T) {}
// 拷贝构造函数:共享数据,计数+1
QSharedDataPointer(const QSharedDataPointer& other) : d(other.d) {
if (d) d->ref.ref(); // 原子操作:计数+1
}
// 析构函数:计数-1,若为0则释放数据
~QSharedDataPointer() {
if (d && !d->ref.deref()) // 原子操作:计数-1,返回是否为0
delete d; // 无共享时释放内存
}
// 赋值运算符:先减少当前数据计数,再共享新数据
QSharedDataPointer& operator=(const QSharedDataPointer& other) {
if (d != other.d) { // 避免自我赋值
if (other.d) other.d->ref.ref(); // 新数据计数+1
if (d && !d->ref.deref()) delete d; // 旧数据计数-1,必要时释放
d = other.d; // 指向新数据
}
return *this;
}
// 写时复制的核心:若共享则复制数据
void detach() {
if (d->ref.load() > 1) { // 若计数>1(存在共享)
T* newData = new T(*d); // 深拷贝数据(调用ShapeData的拷贝构造)
newData->ref.ref(); // 新数据计数初始化为1
if (!d->ref.deref()) delete d; // 旧数据计数-1,必要时释放
d = newData; // 指向新数据(当前对象独占)
}
}
// 重载运算符:修改数据前自动调用detach()
T& operator*() { detach(); return *d; }
T* operator->() { detach(); return d; }
// 只读访问:无需detach(const版本)
const T& operator*() const { return *d; }
const T* operator->() const { return d; }
};
QSharedDataPointer通过detach()函数实现 “写时复制”:当对象需要修改数据时(如调用非 const 成员),detach()会检查是否有其他对象共享数据,若有则复制一份独立数据,确保修改不会影响其他对象。
4、线程安全保障
Qt 的引用计数基于原子操作(Atomic Operation) 实现(通过QAtomicInteger),确保多线程环境下计数修改的安全性。原子操作可以避免 “竞态条件”(如两个线程同时修改计数导致的不一致),保障隐式数据共享在多线程场景中安全使用。
二、应用场景
Qt 中许多容器类和值类型(如 QString、QList、QByteArray、QImage 等)默认使用隐式数据共享。以下是具体示例:
1、QString 的隐式共享
QString str1 = "Hello";
QString str2 = str1; // str1 和 str2 共享同一数据,引用计数为 2
str2[0] = 'h'; // 此时因修改操作,str2 复制数据副本,引用计数变为 1,str1 仍指向原数据
2、容器类的隐式共享
QList<int> list1 = {1, 2, 3};
QList<int> list2 = list1; // 共享数据,引用计数为 2
list2.append(4); // 因修改操作,list2 复制数据副本,引用计数变为 1
三、自定义隐式数据共享实现
下面是一个自定义图形类实现隐式共享的完整代码:
#include <QSharedData>
#include <QSharedDataPointer>
#include <QString>
#include <QDebug>
#include <QAtomicInteger> // 新增头文件
// 共享数据类
class ShapeData : public QSharedData {
public:
ShapeData() : x(0), y(0), color("black") {}
ShapeData(const ShapeData& other)
: QSharedData(other), x(other.x), y(other.y), color(other.color) {
}
~ShapeData() {}
int x;
int y;
QString color;
};
// 公共接口类
class Shape {
public:
Shape() : d(new ShapeData) {}
Shape(int x, int y, const QString& color) {
d = new ShapeData;
d->x = x;
d->y = y;
d->color = color;
}
// 拷贝构造函数和赋值运算符自动处理引用计数
// 读取属性(无需复制数据)
int x() const { return d->x; }
int y() const { return d->y; }
QString color() const { return d->color; }
// 修改属性(需调用detach())
void setX(int x) {
d.detach();
d->x = x;
}
void setY(int y) {
d.detach();
d->y = y;
}
void setColor(const QString& color) {
// 写时复制的关键调用
d.detach();
d->color = color;
}
// 调试用:打印引用计数
int refCount() const {
// 使用QAtomicInteger<int>::load()替代
return d->ref.loadRelaxed();
}
private:
QSharedDataPointer<ShapeData> d;
};
// 使用示例
int main() {
Shape shape1(10, 20, "red");
qDebug() << "Shape1 ref count:" << shape1.refCount(); // 输出: 1
Shape shape2 = shape1;
qDebug() << "Shape1 ref count:" << shape1.refCount(); // 输出: 2
qDebug() << "Shape2 ref count:" << shape2.refCount(); // 输出: 2
shape2.setColor("blue"); // 修改shape2,触发数据复制
qDebug() << "Shape1 ref count:" << shape1.refCount(); // 输出: 1
qDebug() << "Shape2 ref count:" << shape2.refCount(); // 输出: 1
return 0;
}

- 🚀 个人简介:优快云「博客新星」TOP 10 , C/C++ 领域新星创作者
- 💟 作 者:锡兰_CC ❣️
- 📝 专 栏:【OpenCV • c++】计算机视觉
- 🌈 若有帮助,还请关注➕点赞➕收藏,不行的话我再努努力💪💪💪
更多专栏订阅推荐:

157

被折叠的 条评论
为什么被折叠?



