为什么在调用QXYSeries :: replace()之后size()未超过Capacity()的情况下QVector的地址会发生变化? [英] Why does the address of QVector change when size() has not surpassed capacity() after calling QXYSeries::replace()?

查看:139
本文介绍了为什么在调用QXYSeries :: replace()之后size()未超过Capacity()的情况下QVector的地址会发生变化?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个小的测试程序,可以将值插入QVector中.QVector在开始时被保留,以分配最少的内存.我确认容量是正确的,并且QVector的大小没有超过容量.

I have a small test program that inserts values into a QVector. The QVector is reserved at start to have a minimum amount of memory allocated. I verify that the capacity is correct and that the size of the QVector does not surpass the capacity.

我观察到将数据附加到QVector不会更改第一个元素的地址.到目前为止一切都很好.

I observe that appending data to the QVector does not change the address of the first element. All good this far.

然后,我使用QSplineSeries类的replace()方法将数据从矢量复制到图表.调用此方法后,我再次检查了QVector中第一个元素的地址,令我惊讶的是地址已更改!

I then use the replace() method of the QSplineSeries class to copy the data from the vector to the chart. I again check the address of the first element in the QVector after calling this method and to my surprise the address has changed!

每次调用此方法都会发生这种情况,我想了解为什么会发生这种情况.我怀疑可能与Qt的容器的隐式共享有关,但是我不明白为什么这应该更改此容器的地址.

This happens on every call to this method and I want to understand why this happens. I suspect maybe there's something related to Qt's container's implicit sharing, but I don't see why this should change the address of this container.

示例代码和输出如下所示.

The example code and output is shown below.

#include <QApplication>
#include <QDebug>
#include <QtCharts/QChart>
#include <QtCharts/QChartView>
#include <QtCharts/QSplineSeries>
#include <memory>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    auto chart = std::make_unique<QtCharts::QChartView>(new QtCharts::QChart());
    chart->chart()->addSeries(new QtCharts::QSplineSeries);

    QVector<QPointF> vector;
    vector.reserve(1000);

    while (true) {
        vector.append(QPointF(0, 0));
        qDebug() << "1. Size: " << vector.size() << " Capacity: " << vector.capacity();
        qDebug() << "1. Address of first element: " << &vector[0];
        auto series = dynamic_cast<QtCharts::QSplineSeries*>(chart->chart()->series().at(0));
        series->replace(vector);
        qDebug() << "2. Size: " << vector.size() << " Capacity: " << vector.capacity();
        qDebug() << "2. Address of first element: " << &vector[0];
    }

    return a.exec();
}

输出

  1. 大小:1容量:1000
  2. 第一个元素的地址:0x222725d61e8
  3. 大小:1容量:1000
  4. 第一个元素的地址:0x222725dc0d8
  5. 大小:2容量:1000
  6. 第一个元素的地址:0x222725dc0d8
  7. 大小:2容量:1000
  8. 第一个元素的地址:0x222725d61e8
  9. 大小:3容量:1000
  10. 第一个元素的地址:0x222725d61e8
  11. 大小:3容量:1000
  12. 第一个元素的地址:0x222725dc0d8
  13. 大小:4容量:1000
  14. 第一个元素的地址:0x222725dc0d8
  15. 大小:4容量:1000
  16. 第一个元素的地址:0x222725d61e8
  17. 大小:5容量:1000
  18. 第一个元素的地址:0x222725d61e8
  19. 大小:5容量:1000
  20. 第一个元素的地址:0x222725dc0d8

链接到的源代码QSplineSeries :: replace(QVector)在这里调用

编辑

我刚刚意识到QVector的赋值运算符在QSplineSeries :: replace中被调用,它看起来像

I just realized that the assignment operator of QVector gets called in QSplineSeries::replace, which looks like this. This means that the internal vector in QSplineSeries copies the QVector I pass in, but I still don't see why the QVector I pass in changes it's address.

推荐答案

您遇到了Qt的容器与标准库的容器不同的一种方法.具体来说,Qt使用隐式共享和写时复制.这意味着在制作容器副本时,副本和原始副本都指向同一数据.仅当尝试更改其中一个容器的内容时​​,才进行深层复制.(即使它是原始容器,被更改的容器也会为其数据获取一个新的地址.)

You've encountered one of the ways in which Qt's containers differ from those of the standard library. Specifically, Qt uses implicit sharing and copy-on-write. That means that when a copy of a container is made, both the copy and the original point to the same data. It is only when an attempt is made to change the contents of one of the containers that a deep copy is made. (The container being changed gets a new address for its data, even if it was the original container.)

让我们将其应用于您的示例.首先,您获得 QVector 的统计信息.接下来,您调用 QSplineSeries :: replace(QVector< QPointF>).此函数的目的是将提供的 QVector 复制到 QSplineSeries 的数据中.此副本将在函数的返回中保留下来.(该参数还会创建向量的副本&ndash;它不会被引用–传递,但是当函数返回时该副本将被销毁.)因此,当您到达下一行时, vector 将共享数据另一个容器,特别是 chart 中的一个容器.到目前为止,一切都很好.

Let's apply this to your example. First you get stats for your QVector. Next you call QSplineSeries::replace(QVector<QPointF>). The point of this function is to copy the provided QVector into the data of the QSplineSeries. This copy will survive the function's return. (The parameter also creates a copy of the vector – it's not passed by reference – but that copy is destroyed when the function returns.) So when you get to the next line, vector is sharing data with another container, specifically the one deep within chart. So far, so good.

检查 vector 的大小和容量仍然很好.但随后您使用 operator [] .这将返回对向量元素的非恒定引用.这意味着您可以对其进行写操作.是否实际写都没关系.该对象无法知道您将如何使用该引用,因此它必须为可能的写操作做准备.由于数据是共享的,因此将触发深层副本. vector 中的数据将复制到新的内存块中,您可以覆盖该内存块,而不会影响 chart .因此,内存地址发生了变化.

Checking the size and the capacity of vector is still good. But then you use operator[]. This returns a non-constant reference to an element of the vector. That means you can write to it. It doesn't matter whether or not you actually write to it. The object has no way of knowing what you will do with the reference, so it must prepare for a potential write. Since the data is shared, a deep copy is triggered. The data in vector is copied to a new chunk of memory that you can overwrite without affecting chart. Hence the memory address changes.

如果要完成分析而不触发深层复制,请将& vector [0] 更改为& vector.at(0) vector.constData().这些提供了对数据的 const 限定访问权限,因此它们不会触发深层副本.

If you want to accomplish your analysis without triggering a deep copy, change &vector[0] to &vector.at(0) or to vector.constData(). These provide const-qualified access to the data, so they do not trigger a deep copy.

注意: const QVector 使用的 operator [] 版本会返回一个常量引用,因此不会触发深层副本.

Note: The version of operator[] used by a const QVector returns a constant reference, so that will not trigger a deep copy. The non-const nature of vector is a factor here.

这篇关于为什么在调用QXYSeries :: replace()之后size()未超过Capacity()的情况下QVector的地址会发生变化?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆