引用官方文档的解释
共享类由指向共享数据块的指针组成,该指针包含引用计数和数据
创建共享对象时,它将引用计数设置为 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();
运行结果
以上可以看出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();
}
结果
看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()再进行赋值
detach()会判断引用计数是否大于1,如果大于1则创建新的对象
综上,构造自己的隐式共享类,能够像上面所说的QString一样,进行拷贝、只读操作时候进行浅拷贝,发生赋值操作时进行深拷贝
有隐式共享,也就有显示共享
显示共享,即一个对象对新的对象拷贝后,给新对象赋值,旧的对象也跟着变化,即两个对象都指向同一个数据块
Qt同样也提供相关的实现,在上面类的基础上,把QSharedDataPointer换成QExplicitlySharedDataPointer即可实现显示共享类,其他代码不变
替换后的运行效果如下
进行赋值操作后e1和e2的值是一样的,是不是有智能指针那味道了