QTL 容器 与 STL(1)- 写时复制

QTL 与 STL 最大的区别之一

隐式共享,引用计数,写时复制

【注】STL 的 string 也具有 写时复制 技术

写时复制

  (Copy-On-Write)技术,就是编程界“懒惰行为”——拖延战术的产物。

   1、复制的时候共享内存,只有修改(或 [ ] 操作)的时候,才分配新内存

QVector<int> v1;
v1 << 1 << 2 << 3 << 4 << 5;
QVector<int> v2(v1);          // 此时v2与v1共享数据(内存)
v2[1] = 8;                    // 写动作产生,v2被分配新内存
qDebug() << v1 << endl << v2;

 A) 如下图所示,将 v1 复制给 v2时 这两个容器的地址是一样的

  B)使用 [ ] 操作时,给 v2 重新分配内存,两个容器的地址不一样:

  2、写时复制的情况下,同时改变多个容器的值:

// 第一种情况;
QVector<int> v1;
v1 << 1 << 2 << 3 << 4 << 5;
int *p = &v1[1];                 // 声明一个指针指向 v1 的第二个数据
QVector<int> v2(v1);      // 此时v2与v1共享数据(内存)
*p = 8;                              // 使用指针对 v1 数据进行修改
qDebug() << v1 << endl << v2;

// 第二种情况;
QVector<int> aV, bV;
aV.resize(20);

QVector<int>::iterator i = aV.begin();
bV = aV; //此时 aV bV 共享数据(内存);
*i = 4; //此时容器 aV bV 的数据同时改变了;

我们用指向v1的指针修改v1,结果v1与v2的数据都被改变了。原因就在于“利用指针修改内存值”这种写行为无法被QVector类侦测到,因而不能触发其复制机制,当我们在使用qDebug输出v1与v2的值时,他们两者依然共享着同一段内存,因此输出相同的结果。而这种结果大多数情况下都不是程序员想要的,编写Qt代码应该十分小心这个问题。

【注意】

对于 QList 或者 QVector,我们应该使用 at() 函数而不是  []  操作符进行只读访问。

原因是 [] 操作符既可以是左值又可以是右值,这让 Qt 容器很难判断到底是左值还是右值,这意味着无法进行隐式数据共享;

at() 函数不能作左值,因此可以进行隐式数据共享。

另外一点是,对于 begin()end() 以及其他一些非 const 遍历器,由于数据可能改变,因此 Qt 会进行深复制。为了避免这一点,要尽可能使用 const_iteratorconstBegin() constEnd()

3、规避 无法发生的情况【需要重载多个函数,不方便使用,故 QTL未实现】

解决方案:从重载operator[]入手。

A)但是这个操作符比较特殊,比如在

int i = v2[1];

所示的情况下,作为只读情况,我们不需要让v2独立出来,因此无需复制。

B)而在下面这种情况,作为写入的情况,v2中的内容即将被修改,因此需要马上复制出自己一份独立的数据出来。

v2[1] = 10;

C)剩下还有一种就上面所示的情况了,v1[1]被指针定位,我们根本不能确定用户取到它之后会不会修改它,如果不修改而我们又在operator[]内做了复制工作岂不是浪费;如果修改了,我们却在当时没做复制工作,之后就没机会了,就像我们上面看到的例子一样。

遗憾的是,C++并不能区分[]符是在以上哪种情况中被调用的,一概复制可能会浪费,一概不复制又会出问题,怎么做呢,QByteArray的设计给了我们答案(QString类似,至于QVector等容器并未采用此方法的原因,后述)。

我们知道对QByteArray调用[]会返回一个char,因此可以写段类似的代码看看:

QByteArray str1("HelloWorld");
QByteRef c = str1[2];
QByteArray str2(str1);
c = 'M';
qDebug() << str1 << " " << str2;

查看打印结果,没有问题,只有str1被修改了。

在考虑QByteRef到底是个什么东西的时候,我们回头来思考之前的问题:虽然我们不能确定operator []是在左值还是右值的情况被调用,但是我们可以让这个函数返回一个代理类(Proxy Class),然后等待看看这个Proxy类如何被运用——如果它被读取,我们就将operator[]的调用视为一个读取动作,如果它被写,我们就将operator[]的调用视为一个写动作,执行复制行为。好吧,正如你所猜想的,QByteRef正是这样的一个代理类。

QByteArray中重载operator[]的代码如下,除了返回一个QByteRef对象之外什么也没做:

inline QByteRef QByteArray::operator[](int i)
{ Q_ASSERT(i >= 0); return QByteRef(*this, i); }
inline QByteRef QByteArray::operator[](uint i)
{ return QByteRef(*this, i); }

而QByteRef作为QByteArray的内嵌类不到20行:

class Q_CORE_EXPORT QByteRef {
    QByteArray &a;
    int i;
    inline QByteRef(QByteArray &array, int idx)
        : a(array),i(idx) {}
    friend class QByteArray;
public:
    inline operator char() const
        { return i < a.d->size ? a.d->data()[i] : char(0); }
    inline QByteRef &operator=(char c)
        { if (i >= a.d->size) a.expand(i); else a.detach();
          a.d->data()[i] = c;  return *this; }
    inline QByteRef &operator=(const QByteRef &c)
        { if (i >= a.d->size) a.expand(i); else a.detach();
          a.d->data()[i] = c.a.d->data()[c.i];  return *this; }
    inline bool operator==(char c) const
    { return a.d->data()[i] == c; }
    inline bool operator!=(char c) const
    { return a.d->data()[i] != c; }
    inline bool operator>(char c) const
    { return a.d->data()[i] > c; }
    inline bool operator>=(char c) const
    { return a.d->data()[i] >= c; }
    inline bool operator<(char c) const
    { return a.d->data()[i] < c; }
    inline bool operator<=(char c) const
    { return a.d->data()[i] <= c; }
};

当QByteRef作为右值时(被读取),operator char()被调用,QByteRef被隐式转换为char,直接取出其内部数据;当operator =被调用时,意味着QByteRef现在作为左值(被写入),因此调用detach()函数将共享内存分离并进行写入。


现在我们可以轻松地分辨operator []的左值与右值运用了,但当然这也是有的弊端的,原先我们使用v1[1]的方式取出来的就是原始数据类型,比如QVector<int>,我们可以对v1[1]使用+,-,++,--,等等操作符,如果是QVector<MyClass>还可以调用我们自己定义的member function。但是我们一旦开始使用代理类,如果你不同意,编译器可不会让++,--,<,>等东西施加在QXXRef这种类型上。如果我们还想按原来的方式使用,就得重载一大堆函数了,就如上面的代码中后面的代码所示。

好在呢,QByteArray、QString与其他QTL不同,它们内部总是char类型数据,因此重载char相关的操作符就可以了。而QVector、QList等等这种内部数据类型由用户决定的容器就不方便这么做了,也是解释上面QVector为什么不使用代理类的原因。因此,在使用QVector、QList等模板类时,使用指针修改可能已经被隐式共享的对象时,一定要多加小心

发布了87 篇原创文章 · 获赞 46 · 访问量 8万+

猜你喜欢

转载自blog.csdn.net/LearnLHC/article/details/91983462
今日推荐