具有数据共享能力的类包含了一个指向共享数据块的指针。这个数据块包含了数据本身以及数据的引用计数。当共享对象创建出来时,引用计数被设置为 1。当新的对象引用到共享数据时,引用计数增加;当对象引用不再引用数据时,引用计数减少。当引用计数变为 0 时,共享数据被删除。

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

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

    我们可以使用和QSharedDataPointer类实现自己的隐式数据共享类。

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

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

    1. p1.load("image.bmp");
    2.  
    3. paint.begin(&p2); // 从此,p2 与 p1 分道扬镳
    4. paint.end();

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