【Qt笔记】隐式数据共享

Qt 中许多 C++ 类使用了隐式数据共享技术,来最大化资源利用率和最小化拷贝时的资源消耗。看成为参数传递时,具备隐式数据共享的类即安全又高效。在数据传递时,实际上只是传递了数据的指针(这一切都是隐含帮你完成的),而只有在函数发生须要写入的状况时,数据才会被拷贝(也就是一般所说的写时复制)。本章咱们将介绍有关隐式数据共享的相关内容,以便为恰当地使用前面所介绍的容器夯实基础。安全

 

具备数据共享能力的类包含了一个指向共享数据块的指针。这个数据块包含了数据自己以及数据的引用计数。当共享对象建立出来时,引用计数被设置为 1。当新的对象引用到共享数据时,引用计数增长;当对象引用再也不引用数据时,引用计数减小。当引用计数变为 0 时,共享数据被删除。多线程

在咱们操做共享数据时,实际有两种拷贝对象的方法:咱们一般称其为深拷贝和浅拷贝。深拷贝意味着要从新构造一个全新的对象;浅拷贝则仅仅复制引用,也就是上面所说的那个指向共享数据块的指针。深拷贝对内存和 CPU 资源都是很昂贵的;浅拷贝则很是快速,由于它仅仅是设置一个新的指针,而后将引用计数加 1。具备隐式数据共享的对象,其赋值运算符使用的是浅拷贝来实现的。函数

这种隐式数据共享的好处是,程序不须要拥有没必要要的重复数据,减小数据拷贝的需求。重复数据的代价是下降内存使用率(由于内存存储了更多重复的数据)。经过数据共享,对象能够更简单地做为值来传递以及从函数中返回。性能

隐式数据共享是在底层自动完成的,程序人员无需关心。这也是“隐式”一词的含义。从 Qt4 开始,即便在多线程程序中,隐式数据共享也是起做用的。在不少人看来,隐式数据共享和多线程是不兼容的,这是由引用计数的实现方式决定的。可是,Qt 使用了原子性的引用计数来避免多线程环境下可能出现的执行顺序打断的行为。须要注意的是,原子引用计数并不能保证线程安全,仍是须要恰当的锁机制。这种观点对全部相似的场合都是适用的。原子引用计数可以保证的是,线程确定操做本身的数据,线程本身的数据是安全的。总的来讲,从 Qt4 开始,你能够放心使用隐式数据共享的类,即便在多线程环境下。线程

咱们可使用QSharedDataQSharedDataPointer类实现本身的隐式数据共享类。指针

当对象即将被修改,而且其引用计数大于 1 时,隐式数据共享自动将数据从共享块中拿出。隐式共享类必须控制其内部数据,在任何修改其数据的函数中,将数据自动取出。code

QPen使用了隐式数据共享技术,咱们以QPen为例,看看隐式数据共享是如何起做用的:对象

void QPen::setStyle(Qt::PenStyle style)
{
    detach(); // 从共享区取出数据
    d->style = style; // 设置数据(更新)
}

void QPen::detach()
{
    if (d->ref != 1) {
        ... // 执行深拷贝
    }
}

凡是支持隐式数据共享的 Qt 类都支持相似的操做。用户甚至不须要知道对象其实已经共享。所以,你应该把这样的类看成普通类同样,而不该该依赖于其共享的特点做一些“小动做”。事实上,这些类的行为同普通类同样,只不过添加了可能的共享数据的优势。所以,你大可使用按值传参,而无须担忧数据拷贝带来的性能问题。例如:内存

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();

上例中,p1 和 p2 在QPainter::begin()一行以前都是共享数据的,直到这一语句。由于该语句开始,p2 就要被修改了。资源

注意,前面已经提到过,不要在使用了隐式数据共享的容器上,在有非 const STL 风格的遍历器正在遍历时复制容器。另外还有一点,对于QList或者QVector,咱们应该使用at()函数而不是 [] 操做符进行只读访问。缘由是 [] 操做符既能够是左值又能够是右值,这让 Qt 容器很难判断究竟是左值仍是右值,这意味着没法进行隐式数据共享;而at()函数不能做左值,所以能够进行隐式数据共享。另一点是,对于begin()end()以及其余一些非 const 遍历器,因为数据可能改变,所以 Qt 会进行深复制。为了不这一点,要尽量使用const_iteratorconstBegin()constEnd()

相关文章
相关标签/搜索