• Qt隐式共享机制


    1、浅拷贝

    浅拷贝-引用类型。浅拷贝是指源对象与拷贝对象共用一份实体,仅仅是引用的变量不同(名称不同),对其中任何一个对象的改动都会影响另外一个对象。

    2、深拷贝

    而深拷贝-值类型。深拷贝是指源对象与拷贝对象互相独立,其中任何一个对象的改动都不会对另外一个对象造成影响。

    3、隐式共享:

    隐式共享又叫做回写复制。当两个对象共享同一份数据时(通过浅拷贝实现数据块的共享),如果数据不改变,不进行数据的复制。而当某个对象需要改变数据时则执行深拷贝

    采用隐式共享技术,将深拷贝和浅拷贝有机地结合起来。

    Qt中许多常用的类都使用了隐式共享技术,如QString、QImage、容器类、绘图相关类等等。

    QString example:

    本文福利, 免费领取Qt开发学习资料包、技术视频,内容包括(C++语言基础,Qt编程入门,QT信号与槽机制,QT图像绘制,QT网络,QT数据库编程,QT项目实战,QT嵌入式开发,Quick模块等等)↓↓↓↓↓↓见下面↓↓文章底部点击免费领取↓↓

    1. QString str1 = "ubuntu";
    2. QString str2 = str1; //str2 = "ubuntu"
    3. str2[2] = "m"; //str2 = "ubmntu",str1 = "ubuntu"
    4. str2[0] = "o"; //str2 = "obmntu",str1 = "ubuntu"
    5. str1 = str2; //str1 = "obmntu",

    解释:

    line1: 初始化一个内容为"ubuntu"的字符串;

    line2: 将字符串对象str1赋值给另外一个字符串str2(由QString的拷贝构造函数完成str2的初始化)。

    在对str2赋值的时候,会发生一次浅拷贝,导致两个QString对象都会指向同一个数据结构。该数据结构除了保存字符串“ubuntu”之外,还保存一个引用计数器,用来记录字符串数据的引用次数。此处,str1和str2都指向同一数据结构,所以此时引用计数器的值为2.

    line3: 对str2做修改,将会导致一次深拷贝,使得对象str2指向一个新的、不同于str1所指的数据结构(该数据结构中引用计数器值为1,只有str2是指向该结构的),同时修改原来的、str1所指向的数据结构,设置它的引用计数器值为1(此时只有str1对象指向该结构);并在这个str2所指向的、新的数据结构上完成数据的修改。引用计数为1就意味着该数据没有被共享。

    line4: 进一步对str2做修改,不过不会引起任何形式的拷贝,因为str2所指向的数据结构没有被共享。

    line5: 将str2赋给str1.此时,str1修改它指向的数据结构的引用计数器的值位0,表示没有QString类的对象再使用这个数据结构了;因此str1指向的数据结构将会从从内存中释放掉;这一步操作的结构是QString对象str1和str2都指向了字符串为“obmntu”的数据结构,该结构的引用计数为2。

    QPen example:

    1. void QPen::setStyle(Qt::PenStyle style)
    2. {
    3. detach(); // detach from common data
    4. d->style = style; // set the style member
    5. }
    6. void QPen::detach()
    7. {
    8. if (d->ref != 1)
    9. {
    10. ... // perform a deep copy
    11. }
    12. }

    使用隐式共享的QPen类与更改内部数据的所有成员函数中的共享数据分离。

    隐式共享主要发生在幕后; 程序员很少需要担心它。 但是,Qt的容器迭代器具有与STL不同的行为。

    隐式共享对STL样式的迭代器有另一个影响:当迭代器在该容器上处于活动状态时,应避免复制容器。 迭代器指向内部结构,如果复制容器,则应该非常小心迭代器。 例如:

    Qt Vector iterator example:

    1. QVector<int> a, b;
    2. a.resize(100000); // make a big vector filled with 0.
    3. QVector<int>::iterator i = a.begin();
    4. // WRONG way of using the iterator i:
    5. b = a;
    6. /*
    7. Now we should be careful with iterator i since it will point to shared data
    8. If we do *i = 4 then we would change the shared instance (both vectors)
    9. The behavior differs from STL containers. Avoid doing such things in Qt.
    10. */
    11. a[0] = 5;
    12. /*
    13. Container a is now detached from the shared data,
    14. and even though i was an iterator from the container a, it now works as an iterator in b.
    15. Here the situation is that (*i) == 0.
    16. */
    17. b.clear(); // Now the iterator i is completely invalid.
    18. int j = *i; // Undefined behavior!
    19. /*
    20. The data from b (which i pointed to) is gone.
    21. This would be well-defined with STL containers (and (*i) == 5),
    22. but with QVector this is likely to crash.
    23. */

    4、自定义隐式共享类

    实现自己的隐式共享类时,请使用QSharedDataQSharedDataPointer类。

    下面,我们以一个员工类为例,来实现一个隐式共享类。步骤如下:

    定义类Emplyee,该类只有一个唯一的数据成员,类型为QSharedDataPointer

    定义类EmployeeData类,其派生自QSharedData。该类中包含的就是原本应该放在Employee类中的那些数据成员。

    类定义如下:

    C++ Code

    1. #include
    2. #include
    3. class EmployeeData : public QSharedData
    4. {
    5. public:
    6. EmployeeData() : id(-1) { }
    7. EmployeeData(const EmployeeData &other)
    8. : QSharedData(other), id(other.id), name(other.name) { }
    9. ~EmployeeData() { }
    10. int id;
    11. QString name;
    12. };
    13. class Employee
    14. {
    15. public:
    16. Employee()
    17. {
    18. d = new EmployeeData;
    19. }
    20. Employee(int id, const QString &name)
    21. {
    22. d = new EmployeeData;
    23. setId(id);
    24. setName(name);
    25. }
    26. Employee(const Employee &other)
    27. : d (other.d)
    28. {
    29. }
    30. void setId(int id)
    31. {
    32. d->id = id;
    33. }
    34. void setName(const QString &name)
    35. {
    36. d->name = name;
    37. }
    38. int id() const
    39. {
    40. return d->id;
    41. }
    42. QString name() const
    43. {
    44. return d->name;
    45. }
    46. private:
    47. QSharedDataPointer d;
    48. };

    解释:

    在Employee类中,要注意这个数据成员d。所有对employee数据的访问都必须经过d指针的operator->()来操作。对于写访问,operator->()会自动的调用detach(),来创建一个共享数据对象的拷贝,如果该共享数据对象的引用计数大于1的话。也可以确保向一个Employee对象执行写入操作不会影响到其他的共享同一个EmployeeData对象的Employee对象。

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

    在幕后,无论何时一个Employee对象被拷贝、赋值或作为参数传,QSharedDataPointer会自动增加引用计数;无论何时一个Employee对象被删除或超出作用域,QSharedDataPointer会自动递减引用计数。当引用计数为0时,共享的EmployeeData对象会被自动删除。

    void setId(int id) { d->id = id; }

    void setName(const QString &name) { d->name = name; }

    在Employee类的非const成员函数中,无论何时d指针被解引用,QSharedDataPointer都会自动的调用detach()函数来确保该函数作用于一个数据拷贝上。并且,在一个成员函数中,如果对d指针进行了多次解引用,而导致调用了多次detach(),也只会在第一次调用时创建一份拷贝。

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

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

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

    还有,没必要为Employee类实现拷贝构造函数或赋值运算符,因为C++编译器提供的拷贝构造函数和赋值运算符的逐成员拷贝就足够了。

    因为,我们唯一需要拷贝的就是d指针,而该指针是一个QSharedDataPointer,它的operator=()仅仅是递增了共享对象EmployeeData的引用计数。

    如果要使用显式共享,请使用QExplicitySharedDataPointer

    5、总结

    最大化资源有效利用,最小化复制克隆操作。

    本文福利, 免费领取Qt开发学习资料包、技术视频,内容包括(C++语言基础,Qt编程入门,QT信号与槽机制,QT图像绘制,QT网络,QT数据库编程,QT项目实战,QT嵌入式开发,Quick模块等等)↓↓↓↓↓↓见下面↓↓文章底部点击免费领取↓↓

  • 相关阅读:
    服务器数据库三级等保的一些修改步骤
    决策树(Decision tree)基本原理与基于scikit-learn的实现
    CCNA课程实验-12-NAT
    HTML基础学习第五篇(HTML表单与输入)
    【无标题】
    数据结构—前缀树Trie的实现原理以及Java代码的实现
    如何在矩池云上安装语音识别模型 Whisper
    docker安装与镜像基本操作
    Redis高可用方案
    Linux中断和中断处理 “上部分”
  • 原文地址:https://blog.csdn.net/m0_60259116/article/details/127691474