Qt机制——隐式共享

何为隐式共享?

引用官方文档的解释

共享类由指向共享数据块的指针组成,该指针包含引用计数和数据

创建共享对象时,它将引用计数设置为 1。每当新对象引用共享数据时,引用计数就会增加,当对象取消引用共享数据时,引用计数会减少。 当引用计数为零时,共享数据被删除

在处理共享对象时,有两种复制对象的方法。

  • 深拷贝--复制新的对象
  • 浅拷贝--引用拷贝,即只是一个指向共享数据块的指针

就内存和 CPU 而言,进行深度复制可能会很昂贵。 进行浅拷贝非常快,因为它只涉及设置指针和增加引用计数。

隐式共享对象的对象赋值(使用 operator=())是使用浅拷贝实现的

共享的好处是程序没必要进行无畏的复制数据,从而减少内存使用和数据复制。 对象可以很容易地被赋值,作为函数参数发送,并从函数中返回 。

Qt 中的许多 C++ 类使用隐式数据共享来最大化资源使用并最小化复制。 当作为参数传递时,隐式共享类既安全又高效,因为只传递指向数据的指针,并且只有在函数写入时才复制数据,即写时复制。

Qt中很多类都支持隐式共享,以QString为例,看以下代码

    QString str1 = "123";
    qDebug() << __FUNCTION__ << "song" << "str1 ptr:" << str1.data_ptr();
    QString str2 = str1;
    qDebug() << __FUNCTION__ << "song" << "str2 ptr:" << str2.data_ptr();
    str2.at(0);
    qDebug() << __FUNCTION__ << "song" << "str2 ptr:" << str2.data_ptr();
    str2[0] = '0';
    qDebug() << __FUNCTION__ << "song" << "str2 ptr:" << str2.data_ptr();

运行结果

  1.  当str2执行赋值构造函数时,可以看到str2的数据指针和str1是一样的
  2. 当str2调用at()函数,str2的数据指针没有变化
  3. 当str2执行str2[0] = '0';赋值操作后,str的数据指针发生了变化

以上可以看出str2进行只读操作不会发生“写时复制”,当str发生赋值操作是才会触发

再看以下代码

    QString str1 = "123";
    qDebug() << __FUNCTION__ << "song" << "str1 ptr:" << str1.data_ptr();
    QString str2 = str1;
    qDebug() << __FUNCTION__ << "song" << "str2 ptr:" << str2.data_ptr();
    str1 = "223";
    qDebug() << __FUNCTION__ << "song" << "str2 ptr:" << str2.data_ptr() << "str1 ptr:" << str1.data_ptr() << str1 << str2;

 这次是str1进行赋值操作,则是str1的数据指针发生了变化

构造自己的隐式共享类

Qt提供QSharedData和QSharedDataPointer来构造自定义的隐式共享类

官方文档例子如下

#include 
#include 

class EmployeeData : public QSharedData
{
public:
    EmployeeData() : id(-1) { }
    EmployeeData(const EmployeeData &other)
        : QSharedData(other), id(other.id), name(other.name) { }
    ~EmployeeData() { }

    int id;
    QString name;
};
class Employee
{
public:
    Employee() {
        d = new EmployeeData;
        qDebug() << __FUNCTION__ << "song" << d.constData();
    }
    Employee(int id, const QString &name) {
        d = new EmployeeData;
        setId(id);
        setName(name);
        qDebug() << __FUNCTION__ << "song" << d.constData();
    }
    Employee(const Employee &other)
        : d (other.d)
    {
        qDebug() << __FUNCTION__ << "song" << d.constData() << other.d.constData();
    }
    void setId(int id) {
        d->id = id;
    }
    void setName(const QString &name) {
        qDebug() << __FUNCTION__ << "song pre" << d.constData();
        d->name = name;
        qDebug() << __FUNCTION__ << "song end" << d.constData();
    }

    int id() const {
        qDebug() << __FUNCTION__ << "song" << d.constData();
        return d->id;
    }
    QString name() const {
        qDebug() << __FUNCTION__ << "song" << d.constData();
        return d->name;
    }

private:
    QSharedDataPointer d;
};
int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    Employee e1(1001, "Albrecht Durer");
    Employee e2 = e1;
    e1.id();
    e1.setName("Hans Holbein");
    qDebug() << __FUNCTION__ << "song" << e1.name() << e2.name();

    return a.exec();
}

结果

Qt机制——隐式共享_第1张图片

 看main函数的调用,首先是Employee e1(1001, "Albrecht Durer"); 会触发构造函数Employee(int id, const QString &name),构造函数中调用了setId和setName,这时候并不会触发写时复制,因为这时候d的引用计数为1,当大于1时才会触发

接着是生成e2 Employee e2 = e1; ,触发d的赋值,可以看到d值是一样的

e1执行id(),调用只读函数,d值不变

e1执行setName(),执行赋值操作,d进行赋值之前d值未改变,赋值完成后d值发生了变化

执行d->name = name; 会触发QSharedDataPointer的operator->()函数,该函数先执行detach()再进行赋值

Qt机制——隐式共享_第2张图片

detach()会判断引用计数是否大于1,如果大于1则创建新的对象

 Qt机制——隐式共享_第3张图片

 综上,构造自己的隐式共享类,能够像上面所说的QString一样,进行拷贝、只读操作时候进行浅拷贝,发生赋值操作时进行深拷贝

显示共享

有隐式共享,也就有显示共享

显示共享,即一个对象对新的对象拷贝后,给新对象赋值,旧的对象也跟着变化,即两个对象都指向同一个数据块

Qt同样也提供相关的实现,在上面类的基础上,把QSharedDataPointer换成QExplicitlySharedDataPointer即可实现显示共享类,其他代码不变

替换后的运行效果如下

Qt机制——隐式共享_第4张图片

进行赋值操作后e1和e2的值是一样的,是不是有智能指针那味道了

你可能感兴趣的:(Qt)