一、隐式共享类
在Qt中有很多隐式共享类( Implicitly Shared Classes ),什么是隐式共享呢,请参考官方文档的说明。
好吧,翻译一下——
许多C++类隐式地共享数据,使得资源使用最大化,以及对象拷贝最小化。隐式共享类在传参时既安全又高效,因为只传了一个指向数据的指针,并且只有给它写入时数据才会被拷贝。
看了概念之后是不是有点眉目了呢?至少明白隐式共享与C++中参数传值/传引用有关。
二、山重水复
首先定义一个如下的类
class TestClass { public: TestClass(int i) { data = i; } private: int data; };
它有一个整型的数据成员,通过构造函数可以给它传一个整数数据。
然后按照下面的方式定义它的2个对象
TestClass a(5); TestClass b = a;
此时虽然对象a和b中的数据一样,都是5,但它们各自都有一份整型值的拷贝,这就造成了内存浪费。
三、柳暗花明
于是我们想到一个节约内存的方法:
当几个不同对象的数据一样时,保留一份数据就够了;只有当某个对象发生了改变,才用得着一份新的数据。
这涉及到一个新的名词,叫做“引用计数”( reference count )。用一个数来记录有多少对象正在使用同一份数据,当一个新对象被创建,计数加一,当一个对象被销毁,计数减一。如果计数为0,意味着没有对象使用它了,从而将数据释放。
改变上面的程序来简单解释一下——
class DataArea{ public: DataArea(int data) { this->data = data; count = 1; } void increaseRef() { count++; } void decreaseRef() { if (--count == 0) delete this; } private: int count; int data; }; class TestClass{ public: TestClass(int i) { dataArea = new DataArea(i); } TestClass(const TestClass &data) { dataArea = data.dataArea; dataArea->increaseRef(); } TestClass &operator=(const TestClass &testObj) { if (dataArea != testObj.dataArea){ dataArea->decreaseRef(); dataArea = testObj.dataArea; dataArea->increaseRef(); } return *this; } ~TestClass() { dataArea->decreaseRef(); } private: DataArea *dataArea; };
类TestClass是我们直接使用的类,这个类有一个私有成员指向数据区,如果不同对象有相同的数据,那么它们指向同一个数据区。
且看下面的用法:
TestClass a(5); TestClass b = a; b = TestClass(2);
跟踪一下a和b的数据区域指针有什么变化——
TestClass a(5);
1.进入类TestClass的构造函数
2.进入类DataArea的构造函数
3.count = 1、data = 5 (引用计数为1,数据是5)
TestClass b = a;
1.进入类TestClass的拷贝构造函数
b和a中的数据指针指向同一个对象
2.调用计数增加函数
count = 2、data = 3
b = TestClass(2);
1.TestClass(2) —— TestClass的构造函数
2.类DataArea的构造函数
count = 1、data = 4
3.赋值运算符——
b(同时也是a)的数据区指针调用计数减一函数,a计数变为1
4.改变b的数据区指针,指向TestClass(2)对象的数据区
b(同时也是对象TestClass(2))的数据区指针调用计数加一函数
b的计数变为2
5.析构TestClass(2)这个对象,进入计数减一函数
b的计数变为1
当然,程序结束时,会调用a和b的析构函数,计数减为0,释放数据区。
上面这些其实就是我们平时说的“写时复制”(copy on write)。
为了方便理解,上面的例子可能不太好。当一个对象很大很复杂的时候,直接拷贝可能对效率有很大影响,这时就能体现出写时复制的优势了。
四、Qt中的隐式共享
以QString为例,QString中有一个 constData() 函数,该函数返回一个指向QString中存储的数据的指针。
QString a("Diao"); QString b = a; qDebug() << a; qDebug() << b; qDebug() << &a; qDebug() << &b; qDebug() << a.constData(); qDebug() << b.constData(); a[0] = 'M'; qDebug() << a; qDebug() << b; qDebug() << &a; qDebug() << &b; qDebug() << a.constData(); qDebug() << b.constData();
结果如下:
懂了?