Почему адрес QVector изменяется, если size() не превышает capacity() после вызова QXYSeries::replace()?

У меня есть небольшая тестовая программа, которая вставляет значения в QVector. QVector зарезервирован при запуске, чтобы выделить минимальный объем памяти. Я проверяю, что емкость правильная и что размер QVector не превышает емкость.

Я заметил, что добавление данных в QVector не меняет адрес первого элемента. Все хорошо до сих пор.

Затем я использую метод replace() класса QSplineSeries для копирования данных из вектора в диаграмму. Я снова проверяю адрес первого элемента в QVector после вызова этого метода, и, к моему удивлению, адрес изменился!

Это происходит при каждом вызове этого метода, и я хочу понять, почему это происходит. Я подозреваю, что может быть что-то связано с неявным совместным использованием контейнера Qt, но я не понимаю, почему это должно изменить адрес этого контейнера.

Пример кода и вывода показаны ниже.

#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:: заменить, который выглядит как это. Это означает, что внутренний вектор в QSplineSeries копирует переданный мной QVector, но я все еще не понимаю, почему переданный мной QVector меняет свой адрес.

1 ответ

Решение

Вы столкнулись с одним из отличий контейнеров Qt от контейнеров стандартной библиотеки. В частности, Qt использует неявное совместное использование и копирование при записи. Это означает, что при создании копии контейнера и копия, и оригинал указывают на одни и те же данные. Только когда делается попытка изменить содержимое одного из контейнеров, делается глубокая копия. (Изменяемый контейнер получает новый адрес для своих данных, даже если это был исходный контейнер.)

Применим это к вашему примеру. Сначала вы получите статистику по вашемуQVector. Далее вы звонитеQSplineSeries::replace(QVector<QPointF>). Смысл этой функции - скопировать предоставленныйQVector в данные QSplineSeries. Эта копия переживет возврат функции. (Параметр также создает копию вектора - он не передается по ссылке - но эта копия уничтожается, когда функция возвращается.) Итак, когда вы переходите к следующей строке,vector обменивается данными с другим контейнером, в частности с тем, который находится глубоко внутри chart. Все идет нормально.

Проверка размера и вместимости vectorвсе еще хорошо. Но тогда вы используете operator[]. Это возвращает непостоянную ссылку на элемент вектора. Это означает, что вы можете писать на него. Не имеет значения, действительно ли вы пишете ему. У объекта нет возможности узнать, что вы будете делать со ссылкой, поэтому он должен подготовиться к потенциальной записи. Поскольку данные являются общими, запускается глубокая копия. Данные вvector копируется в новый фрагмент памяти, который вы можете перезаписать, не затрагивая chart. Следовательно, адрес памяти меняется.

Если вы хотите выполнить анализ, не вызывая глубокую копию, измените &vector[0] к &vector.at(0) или чтобы vector.constData(). Они обеспечиваютconst-квалифицированный доступ к данным, поэтому они не запускают глубокую копию.

Примечание: версия operator[] используется const QVectorвозвращает постоянную ссылку, поэтому глубокая копия не запускается. Затем на-const природа vector здесь фактор.

Другие вопросы по тегам