zoukankan      html  css  js  c++  java
  • Qt中的隐式共享

    一、【隐式共享】简介

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

    ​ 共享类由一个指向包含引用计数数据的共享数据块的指针组成。

    ​ 当创建共享对象时,它将引用计数设置为1。每当新对象引用共享数据时,引用计数就递增,当对象解引用共享数据时,引用计数就递减。当引用计数变为零时,将删除共享数据。

    ​ 在处理共享对象时,有两种方法复制对象。我们通常谈论:深度拷贝和浅拷贝。深层复制意味着复制一个对象。浅拷贝是一个引用拷贝,也就是一个指向共享数据块的指针。在内存和CPU方面,制作一个深度拷贝可能是昂贵的。进行浅拷贝非常快,因为它只涉及设置指针和增加引用计数。

    ​ 注意:隐式共享对象的对象赋值(使用operator=())是使用浅拷贝实现的。

    ​ 隐式共享的好处是程序不需要进行不必要地数据复制操作,从而减少内存的使用和数据的复制。此外,对象可以很容易地被赋值,作为函数参数传递,并从函数中返回。

    二、隐式共享在开发中的使用

    ​ 隐式共享自动将对象从共享块中分离出来,如果对象即将改变并且引用计数大于1。(这通常被称为写时复制或值语义。)

    ​ 隐式共享类可以控制其内部数据。在任何修改其数据的成员函数中,它都会在修改数据之前自动分离。(但是,需注意容器迭代器的特殊情况,后文将说明这一点!)

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

    如下代码片段:

    void QPen::setStyle(Qt::PenStyle style)
    {
        detach();           // 从公共数据中分离
        d->style = style;   // 设置style成员
    }
    
    void QPen::detach()
    {
        if (d->ref != 1) {
            ...             // perform a deep copy
        }
    }
    

    ​ 如果要更改对象,下面列出的类将自动与公共数据分离。我们甚至不会注意到这些对象是共享的。因此,应该将它们的单独实例视为单独的对象。它们将始终作为独立的对象,但在有些情况下可以共享数据。因此可以将这些类的实例作为参数按值传递给函数,而不必考虑复制开销。

    ​ 例如

    QPixmap p1, p2;
    p1.load("image.bmp");
    p2 = p1;                        // p1 和 p2 共享数据
    
    QPainter paint;
    paint.begin(&p2);               // 将p2从p1中分离出来
    paint.drawText(0,50, "Hi");
    paint.end();
    

    ​ 【警告】:在使用stl风格的迭代器时,在复制隐式共享容器(QMap, QList等),需要特别注意。

    ​ Qt官方提供一个隐式共享类的表格,查看该表可获得Qt中哪些类是隐式共享类。URL:https://doc.qt.io/qt-6/implicit-sharing.html

    三、隐式共享迭代器问题

    ​ 隐式共享对于stl风格的迭代器还有另一个后果:当迭代器在容器上激活时,应该避免复制容器。迭代器指向一个内部结构,如果复制一个容器,此时应特别注意迭代器。例如以下代码片段:

    QList<int> a, b;
    a.resize(100000); // 制作一个大列表,里面填满0。
    
    QList<int>::iterator i = a.begin();
    
    /*-------------------------------------------------------------*/
    
    // 使用迭代器i的错误方法:
    b = a;
    /*
        现在我们应该小心迭代器i,因为它将指向共享数据
        如果我们执行*i = 4,那么我们将改变共享实例(两个向量)
        其行为不同于STL容器。在Qt中不能这样做。
    */
    
    /*-------------------------------------------------------------*/
    
    a[0] = 5;
    /*
        容器a现在与共享数据分离,
        尽管i是容器a的迭代器,它现在作为容器b的迭代器工作。
        这里的情况是(*i) == 0。
    */
    
    b.clear(); // 现在迭代器i完全无效了。
    
    int j = *i; //未定义的行为!
    /*
        来自b(i所指向的)的数据不见了。
        这可以用STL容器(and (*i) == 5)定义,
        但是使用QList,可能会崩溃。
    */
    

    ​ 总而言之:当迭代器在容器上激活时,应该避免复制容器,所有的Qt容器类都应该注意这一点。

    四、线程和隐式共享类

    ​ Qt对它的许多值类使用一种称为隐式共享的优化,尤其是QImage和QString。从Qt 4开始,隐式共享类可以安全地跨线程复制,就像任何其他值类一样。它们是完全可重入的。隐式分享确实是隐式的。

    ​ 在许多人的心目中,隐式共享和多线程是不兼容的概念,因为引用计数通常是这样做的。然而,Qt使用原子引用计数来确保共享数据的完整性,避免引用计数器的潜在损坏。

    ​ 注意,原子引用计数不能保证线程安全性。在线程之间共享隐式共享类的实例时,应该使用适当的锁定。这是对所有重入类(无论是否共享)的相同要求。然而,原子引用计数确实保证了一个线程在其自身、隐式共享类的本地实例上工作是安全的。 所以说可以使用信号和槽机制在不同线程之间传递数据,因为这可以在不需要显式锁定的情况下完成。

    ​ 总之,Qt 4中的隐式共享类实际上是隐式共享的。即使在多线程应用程序中,也可以安全地使用,就像它们是普通的、非共享的、可重入的基于值的类一样。

  • 相关阅读:
    小结 编程练习 制作新按钮,“新窗口打开网站” ,新窗口打开时弹出确认框,是否打开,通过输入对话框,打开的窗口要求,宽400像素,高500像素,无菜单栏、无工具栏。
    关闭窗口(window.close)
    打开新窗口(window.open) open() 方法可以查找一个已经存在或者新建的浏览器窗口。 语法: window.open([URL], [窗口名称], [参数字符串])
    提问(prompt 消息对话框)用于询问一些需要与用户交互的信息。弹出消息对话框(包含一个确定按钮、取消按钮与一个文本输入框)
    确认(confirm 消息对话框)语法:confirm(str); 消息对话框通常用于允许用户做选择的动作,如:“你对吗?”等。弹出对话框(包括一个确定按钮和一个取消按钮)
    警告(alert 消息对话框) 如果你不点击“确定”,就不能对网页做任何操作,这个小窗口就是使用alert实现的
    Socket的3次握手链接与4次断开握手
    大型电子商务网站架构之--分布式可扩展数据库架构
    程序员成长历程的四个阶段
    盘点 Github 所用到的开源项目
  • 原文地址:https://www.cnblogs.com/iriczhao/p/15611249.html
Copyright © 2011-2022 走看看