Qt-自行实现隐式数据共享

概述

在使用QStringQByteArray等容器时,都会碰到一个名词隐式数据共享。说起来比较高大上,通俗一点就是写时复制(copy-on-write),当两个对象共享同一份数据时通过浅拷贝实现数据块的共享,如果数据不改变,不进行数据的复制。而当某个对象需要改变数据时则执行深拷贝。

例子

此处验证QString的隐式数据共享:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
...
QString s1 = "abcdefg";
QString s2 = s1;

// output will be the same
qDebug() << s1.constData();
qDebug() << s2.constData();

// this step makes a deep copy
s2[0] = 'A';

// output will be different
qDebug() << s1.constData();
qDebug() << s2.constData();

自行实现

需要自己实现隐式数据共享,可以依靠Qt提供的QSharedDataQSharedDataPointer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
// 数据实现
class TestData : public QSharedData
{
public:
TestData()
: id(-1)
{
}
TestData(const TestData &other)
: QSharedData(other),
id(other.id),
name(other.name)
{
}
~TestData()
{
}

int id;
QString name;
};

// 类实现
class Test
{
public:
Test()
{
d = new TestData;
}
Test(int id, const QString &name)
{
d = new TestData;
setId(id);
setName(name);
}
Test(const Test &other)
: d (other.d)
{
}
void setId(int id)
{
d->id = id;
}
void setName(const QString &name)
{
d->name = name;
}

int id() const
{
return d->id;
}
QString name() const
{
return d->name;
}

private:
QSharedDataPointer<TestData> d;
};

Test这个类中,要注意这个数据成员d,其为QSharedDataPointer,所有对Test类数据成员的访问都通过这个指针的operator->()来操作。

对于写访问,operator->()会自动调用detach(),来创建一个共享数据对象的拷贝。

如果该共享数据对象的引用计数大于1的话,也可以确保向一个Test对象执行写入操作不会影响到其他的共享同一个TestData对象的Test对象。

TestData继承自QSharedData,它提供了幕后的引用计数。

当无论何时一个Test对象被拷贝、复制或作为参数传值,QSharedDataPointer都会自动增加引用计数;反之,当一个Test对象被删除或超出作用域,引用计数会减少;当引用计数归零,该TestData会被自动删除。

Test类的非const成员函数中,无论指针何时被解引用,QSharedDataPointer都会调用一次detach()以保证该函数作用在一份数据拷贝上。注意,在一个成员函数中,如果对指针进行了多次解引用,而导致调用了多次detach(),也只会在第一次调用时创建一份拷贝。

但在Testconst成员函数中,对指针的解引用不会导致detach()的调用。