前言
所谓的隐式共享,就是多个对象引用同一数据(参考智能指针
),只在修改内容时才创建新的数据副本(即 copy-on-write 写时复制)。大部分 Qt 容器及相关类都是隐式共享的,且提供了 QSharedData 和 QSharedDataPointer 来帮助我们快速地实现自己的隐式共享类(Qt 自带的容器并没有使用该便捷类,是定制化的实现)。
原理
参照 Qt 文档中的示例,要使用 QSharedDataPointer 实现隐式共享,需要定义两个类。一个类继承
自 QSharedData 用于存放数据;另一个类定义一个 QSharedDataPointer 模板成员,模板类型就是上一步的 QSharedData 派生类。
class SharedData : public QSharedData {
};
class SharedObject {
QSharedDataPointer<SharedData> d;
};
从 Qt 源码我们可以看到, QSharedData 主要是维护了一个引用计数的变量:
class QSharedData
{
public:
mutable QAtomicInt ref;
inline QSharedData() noexcept : ref(0) { }
inline QSharedData(const QSharedData &) noexcept : ref(0) { }
// using the assignment operator would lead to corruption in the ref-counting
QSharedData &operator=(const QSharedData &) = delete;
~QSharedData() = default;
};
而 QSharedDataPointer 每次拷贝构造和赋值构造都是浅拷贝,只复制 QSharedData 指针的值,并增加引用计数:
inline QSharedDataPointer(const QSharedDataPointer<T> &o)
: d(o.d) { if (d) d->ref.ref(); }
inline QSharedDataPointer<T> & operator=(const QSharedDataPointer<T> &o) {
if (o.d != d) {
if (o.d)
o.d->ref.ref();
T *old = d;
d = o.d;
if (old && !old->ref.deref())
delete old;
}
return *this;
}
而写时深拷贝主要是借助接口的 const 声明来判断的,如果是非 const 接口,那么就会进行深拷贝,并修改引用计数:
template <class T> class QSharedDataPointer
{
public:
//... ...
inline void detach() { if (d && d->ref.loadRelaxed() != 1) detach_helper(); }
inline T &operator*() { detach(); return *d; }
inline const T &operator*() const { return *d; }
inline T *operator->() { detach(); return d; }
inline const T *operator->() const { return d; }
inline operator T *() { detach(); return d; }
inline operator const T *() const { return d; }
inline T *data() { detach(); return d; }
inline const T *data() const { return d; }
inline const T *constData() const { return d; }
void QSharedDataPointer<T>::detach_helper()
{
T *x = clone();
x->ref.ref();
if (!d->ref.deref())
delete d;
d = x;
}
T *QExplicitlySharedDataPointer<T>::clone()
{
return new T(*d);
}
};
原理上还是很简单的,和智能指针差不多。下面照着示例写一个自己的隐式共享类。
实现
#pragma once
//参照Qt文档:https://doc.qt.io/qt-5/qshareddatapointer.html
#include <QSharedData>
#include <QSharedDataPointer>
#include <QDebug>
//共享的数据
//QSharedData带有原子类型的引用计数
class SharedData : public QSharedData
{
public:
SharedData() {
qDebug()<<"SharedData()";
}
//拷贝构造不是必要的
SharedData(const SharedData &other)
: QSharedData(other),
num(other.num),
name(other.name) {
qDebug()<<"SharedData(const SharedData &other)";
}
~SharedData() {
qDebug()<<"~SharedData()";
}
int getNum() const { return num; }
void setNum(int i) { num = i; }
QString getName() const { return name; }
void setName(const QString &str) { name = str; }
private:
int num{0};
QString name;
};
//隐式共享类
//修改数据时调用QSharedDataPointer的detach创建副本
class SharedObject
{
public:
SharedObject() { d = new SharedData; }
SharedObject(const SharedObject &other) : d(other.d) { }
//非const接口会自动创建副本,而const接口与其他共享引用
int getNum() const { return d->getNum(); }
void setNum(int i) { d->setNum(i); }
QString getName() const { return d->getName(); }
void setName(const QString &str) { d->setName(str); }
private:
QSharedDataPointer<SharedData> d;
};
#include <QCoreApplication>
#include <QDebug>
#include "SharedObject.h"
int main(int argc, char *argv[])
{
QCoreApplication app(argc, argv);
{
SharedObject b1;
b1.setNum(11);
b1.setName("11");
SharedObject b2 = b1;
SharedObject b3 = b1;
qDebug()<<"before set";
b1.setNum(22);
qDebug()<<"after set";
qDebug()<<b1.getNum()<<b1.getName()
<<b2.getNum()<<b2.getName()
<<b3.getNum()<<b3.getName();
}
return app.exec();
}
运行结果:
断点调试查看地址:
参考
Qt 文档:https://doc.qt.io/qt-5/implicit-sharing.html
Qt 文档:https://doc.qt.io/qt-5/qshareddatapointer.html