Подтвердить что ты не робот

Cbegin, вывести недопустимые результаты для пустых контейнеров в Qt 5.2.1

В Qt 5.2.1 как получается, что следующие результаты кода отличаются?

QVector<int> c;
if (c.cbegin() != c.begin())
{
   std::cout << "Argh!" << std::endl;
}

это печатает "argh", но следующее не делает.

QVector<int> c;
if (c.begin() != c.cbegin())
{
    std::cout << "Argh!" << std::endl;
}

Обратите внимание, что переключатели cbegin и begin переключаются. Но если вы меняете состояние контейнера, я имею в виду, например, push_back что-то в нем, он работает правильно. Мне кажется, что перед вызовом любого измененного метода в контейнере cbegin и cend недействительны. Это ошибка или функция?

4b9b3361

Ответ 1

Поведение, которое вы наблюдаете, связано с порядком вызовов, сделанных в QVector::begin и QVector::cbegin. Если вызов первого происходит до вызова последнего, то их возвращаемые значения сравниваются равными, но если вы отменяете порядок, они не сравниваются с равными. Это может быть продемонстрировано с помощью следующего кода:

QVector<int> c;
std::cout << static_cast<void const *>(c.begin()) << std::endl;
std::cout << static_cast<void const *>(c.cbegin()) << std::endl;

Отпечатанные адреса будут одинаковыми. Однако, если вы поменяете порядок двух операторов печати, адреса будут разными.

Если вы вникнете в исходный код для двух функций-членов, вы увидите

inline iterator begin() { detach(); return d->begin(); }
inline const_iterator cbegin() const { return d->constBegin(); }

И прослеживая назад, тела как d->begin(), так и d->constBegin() идентичны. Поэтому разница в вызове QVector::detach() в первом случае. Это определяется как

template <typename T>
void QVector<T>::detach()
{
    if (!isDetached()) {
#if QT_SUPPORTS(UNSHARABLE_CONTAINERS)
        if (!d->alloc)
            d = Data::unsharableEmpty();
        else
#endif
            reallocData(d->size, int(d->alloc));
    }
    Q_ASSERT(isDetached());
}

Объяснение того, что происходит, можно найти здесь.

Контейнеры Qts неявно разделяются - при копировании объекта копируется только указатель на данные. Когда объект изменен, он сначала создает глубокую копию данных, чтобы он не влиял на другие объекты. Процесс создания глубокой копии дня называется detach

Так как итераторы в стиле STL концептуально просто указатели, они непосредственно изменяют базовые данные. Как следствие, неконстантные итераторы отделяются при создании с помощью Container::begin(), так что другие неявно разделенные экземпляры не могут пострадать от изменений.

Итак, если сначала выполняется вызов QVector::begin(), тогда основное хранилище контейнера перераспределяется, а последующий вызов QVector::cbegin() возвращает тот же указатель. Однако, отмените их, и вызов QVector::cbegin() возвращает указатель на общий хранилище до того, как произойдет перераспределение.

Ответ 2

Используемый тестовый код очень похож на отчет об ошибке который был подан в 2012 году. Он был закрыт как недопустимый, потому что

constBegin и начать не следует сравнивать. Когда-либо. Это неверно. (и может быть пойман, со строгими проверками итератора), поэтому здесь ничего не исправить.

Это правда. Но функция begin() перегружена как

 QVector<T>::iterator       QVector::begin();
 QVector<T>::const_iterator QVector::begin() const;

Это неуказанный bevahior, как порядок оценки операндов оператора С++ == не указано. В С++ нет концепции оценки слева направо или справа налево.

Таким образом, в зависимости от компилятора и оптимизации вы получите либо версию begin iterator, либо версию const_iterator.