Qt隐式共享detach函数的理解

隐式数据共享
Qt隐式共享的详细描述,请在Qt Assistant 索引查阅,如下:

以下是本人的理解:

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

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

创建共享对象时,它将引用计数设置为 1。每当新对象引用共享数据时,引用计数就会增加,当对象取消引用共享数据时,引用计数会减少。当引用计数变为零时,共享数据将被删除,类似C++中的共享智能指针。

在处理共享对象时,有两种复制对象的方法:深拷贝和浅拷贝。

  • 深拷贝意味着复制一个对象。
  • 浅拷贝是引用拷贝,即只拷贝指向共享数据块的指针,而不是拷贝指针指向的对象。

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

隐式共享对象的对象分配(使用 operator=())是使用浅拷贝实现的。

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


如下A为对象,且当前只有一个A类型的指针指向该对象,暂且称为pA1,类型为A*,

此时A*的类内部的引用计数refCount 为1。当有N个A类型的指针指向A对象时,即如下图所示:

此时A*的类内部的引用计数refCount 为n,可以看到这些指针都是共享A,并没有单独复制一份A对象,即没有深度复制A对象,也就是所谓的浅拷贝。当需要对某个指针指向的A对象的属性进行更新、修改时,其它指针的调用方此时发现其所指向的A也跟着改变了(因为它们都是指向同一个A对象),这在很多情况下,不是调用方所希望的结果。比如:A是QPen对象,如下代码:


  void QPen::setStyle(Qt::PenStyle style)
  {
      
      d->style = style;   // set the style member
  }

  ......................// 其它代码
QPen* pPen1 = new QPen; // QPen的引用计数为1
QPen* pPen2 = pPen1;    // QPen的引用计数为2

// 刚new出来的QPen对象被pPen2更改了,此时pPen1的调用方也跟着变化了,此时pPen1的调用方
// 功能就会不正确,就会感觉诡异,心里暗想:我明明啥都没改,怎么画笔就变了呢?
pPen2->setStyle(Qt::CustomDashLine); 

刚new出来的QPen对象被pPen2更改了,此时pPen1的调用方也跟着变化了,此时pPen1的调用方
功能就会不正确,就会感觉诡异,心里暗想:我明明啥都没改,怎么画笔就变了呢?detach正是为了解决这个问题而提出!!!对于上面情况,detach()函数内部实现机制是:

  1. 先深度拷贝一个A对象,拿上面的代码来说就是深度复制出一个QPen。
  2. 更新操作只在步骤1深度拷贝出的对象上进行。

即:

 void QPen::setStyle(Qt::PenStyle style)
  {
      detach();           // detach from common data

      // 这里的d是detach()函数深度拷贝出来的新对象,而不再是原来的那个。
      d->style = style;   // set the style member
  }

  void QPen::detach()
  {
      if (d->ref != 1) {
          ...   // 发现引用计数大于1,证明其它地方也在调用该对象,那么就深度拷贝一个该对象
      }
  }

即按下图进行:

pA2即为要更新的对象。先将pA2指向A对象断开,再深度复制一个A,让 pA2指向新复制出来的这个A对象。此时对新复制出来的A无论怎么修改都不会影响到原来的A对象,即非pA2的指针的调用方不会受影响。

对于QSharedDataPointer类型(QSharedDataPointer用法请参考《QPointer、QScopedPointer、QSharedDataPointer等指针用法总结》)的clone函数,当引用计数大于 1 时,clone函数由 detach() 调用以创建新副本。此函数使用运算符 new 并调用类型 T 的拷贝构造函数。QSharedDataPointer类型的的T *data() / T *get(),返回指向共享数据对象的指针。这两个函数也调用 detach()。QSharedDataPointer类型设计clone、data() 、 get()函数的目的就是为了对指向的对象进行更改,所以必须是深度复制才行。QSharedDataPointer类型的const T *constData() 、const T *data()  / const T *get() 设计目的从const就可以看出仅仅是读取指向的对象,而不是进行写入更改操作,故带const的这三个函数不会调用detach,因为浅拷贝就可以完成,用浅拷贝进行读取共享对象不会导致被读取的共享对象更改,所以没必要进行资源高、效率低的深拷贝。

总结:detach发生两个必要条件是:

  • 隐式共享的对象个数超过1个,即引用计数大于1.
  • 即将要detach的对象被更改。
在 C++ 中,隐式调用构造函数是指在某些情况下,编译器会自动调用构造函数来创建一个对象,而不需要程序员显式地调用构造函数。 例如,当我们定义一个类的对象时,编译器会自动调用该类的默认构造函数来创建对象。又例如,当我们用一个已有的对象初始化另一个对象时,编译器也会自动调用该类的拷贝构造函数来完成初始化。 下面是一个示例代码,演示了隐式调用构造函数的情况: ```c++ #include <iostream> using namespace std; class MyClass { public: int value; MyClass() { // 默认构造函数 cout << "default constructor called" << endl; value = 0; } MyClass(int n) { // 带参数的构造函数 cout << "constructor with int called" << endl; value = n; } MyClass(const MyClass& other) { // 拷贝构造函数 cout << "copy constructor called" << endl; value = other.value; } }; int main() { MyClass a; // 隐式调用默认构造函数 MyClass b = 10; // 隐式调用带参数的构造函数 MyClass c(a); // 隐式调用拷贝构造函数 MyClass d = b; // 隐式调用拷贝构造函数 return 0; } ``` 在上面的代码中,当我们定义对象 `a` 时,编译器会自动调用 `MyClass` 类的默认构造函数;当我们定义对象 `b` 时,由于使用了整数 `10` 进行初始化,编译器会自动调用 `MyClass` 类的带参数的构造函数;当我们定义对象 `c` 和 `d` 时,由于使用了已有的对象进行初始化,编译器会自动调用 `MyClass` 类的拷贝构造函数。 需要注意的是,当我们使用 `explicit` 关键字修饰一个构造函数时,就不能进行隐式调用构造函数了,只能使用显式调用构造函数
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值