原文地址 Implicit Sharing | Qt Core 6.5.0
Qt 中的许多 C++ 类使用隐式数据共享来最大化资源使用并最小化复制。 当作为参数传递时,隐式共享类既安全又高效,因为只传递指向数据的指针,并且只有当函数写入数据时才会复制数据,即写时复制。
概述
共享类由指向包含引用计数和数据的共享数据块的指针组成。
创建共享对象时,它会将引用计数设置为 1。每当新对象引用共享数据时,引用计数就会增加,而当对象取消引用共享数据时,引用计数就会减少。 当引用计数变为零时,共享数据将被删除。
在处理共享对象时,有两种复制对象的方法。 我们通常说深拷贝和浅拷贝。 深拷贝意味着复制一个对象。 浅拷贝是引用拷贝,即只是指向共享数据块的指针。 就内存和 CPU 而言,进行深拷贝可能会很昂贵。 制作浅拷贝非常快,因为它只涉及设置指针和增加引用计数。
隐式共享对象的对象分配(使用 operator=())是使用浅拷贝实现的。
共享的好处是程序不需要不必要地复制数据,从而减少内存使用和数据复制。对象可以很容易地被赋值,作为函数参数发送,并从函数返回。
隐式共享主要发生在幕后;程序员很少需要担心它。然而,Qt 的容器迭代器与 STL 中的容器迭代器有不同的行为。阅读隐式共享迭代器问题(后面会讲)。
在多线程应用程序中,会发生隐式共享,但是要注意线程安全问题
在实现您自己的隐式共享类时,请使用 QSharedData 和 QSharedDataPointer 类。
详细介绍
如果对象即将更改并且引用计数大于 1,则隐式共享会自动将对象从共享块中分离出来。 (这通常称为写时复制或值语义。)
隐式共享类可以控制其内部数据。 在修改其数据的任何成员函数中,它会在修改数据之前自动分离。 但是,请注意容器迭代器的特殊情况; 请参阅隐式共享迭代器问题。
使用隐式共享的 QPen 类从所有更改内部数据的成员函数中的共享数据分离。
代码片段:
void QPen::setStyle(Qt::PenStyle style)
{
detach(); // detach from common data
d->style = style; // set the style member
}
void QPen::detach()
{
if (d->ref != 1) {
... // perform a deep copy
}
}
如果对象将要更改,下面列出的类(见原文)会自动从公共数据中分离出来。 程序员甚至不会注意到对象是共享的。 因此,您应该将它们的单独实例视为单独的对象。 它们将始终表现为独立的对象,但具有尽可能共享数据的额外好处。 因此,您可以按值将这些类的实例作为参数传递给函数,而无需担心复制开销。
举例:
QPixmap p1, p2;
p1.load("image.bmp");
p2 = p1; // p1 and p2 share data
QPainter paint;
paint.begin(&p2); // cuts p2 loose from p1
paint.drawText(0,50, "Hi");
paint.end();
在此示例中,p1 和 p2 共享数据直到为 p2 调用 QPainter::begin(),因为绘制像素图将修改它。
隐式共享迭代器问题
隐式共享对 STL 样式的迭代器有另一个影响:当迭代器在容器上处于活动状态时,您应该避免复制容器。 迭代器指向一个内部结构,如果你复制一个容器,你应该非常小心你的迭代器。 例如:
QList<int> a, b;
a.resize(100000); // make a big list filled with 0.
QList<int>::iterator i = a.begin();
// WRONG way of using the iterator i:
b = a; // 此时b和a共享数据
a[0] = 5;
// 修改了a的数据,a拷贝了一份,从共享数据分离
// 但是i仍然指向共享数据,即此时只指向b
b.clear(); // 此时 i 无效
int j = *i; // 读取无效数据,可能会导致crash
/*
The data from b (which i pointed to) is gone.
This would be well-defined with STL containers (and (*i) == 5),
but with QList this is likely to crash.
*/
上面的示例仅显示了 QList 的问题,但所有隐式共享的 Qt 容器都存在该问题。