Qt实现一个隐式共享类(使用QSharedDataPointer)

文章介绍了Qt框架中隐式共享的概念,通过QSharedData和QSharedDataPointer实现数据的共享和写时复制。当多个对象引用同一数据时,只有在修改内容时才会创建数据副本,从而提高效率。文中还展示了如何自定义一个隐式共享类,并给出了运行示例及结果。
摘要由CSDN通过智能技术生成

前言

所谓的隐式共享,就是多个对象引用同一数据(参考智能指针),只在修改内容时才创建新的数据副本(即 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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值