Арифметика на недействительных итераторах

Является ли поведение std::distance неопределенным при вызове пары итераторов std::vector, которые были признаны недействительными при перемещении vector?

Для контекста: я пишу конструкторы копирования и перемещения для класса, который имеет vector данных и vector итераторов, указывающих на эти данные. Как только я перемещаю данные к месту назначения, мне нужно преобразовать вектор итераторов, чтобы он указывал на новый контейнер. Я хотел бы избежать создания промежуточного представления индекса в памяти.


person tsuki    schedule 16.06.2020    source источник
comment
По стандарту - нет, потому что один итератор больше недоступен из другого. На практике это, вероятно, будет работать нормально, но я бы не стал использовать его таким образом.   -  person Rinat Veliakhmedov    schedule 16.06.2020
comment
@RinatVeliakhmedov Согласно стандарту Можно ссылку на абзац?   -  person Thomas Sablik    schedule 16.06.2020
comment
@ThomasSablik Я предполагаю, что часть cppreference здесь взята из стандарта en.cppreference.com/w/ cpp/iterator/distance Если InputIt не является LegacyRandomAccessIterator, поведение не определено, если последний недоступен с первого путем (возможно, многократного) увеличения первого. Если InputIt имеет значение LegacyRandomAccessIterator, поведение не определено, если последний недоступен из первого, а первый недоступен из последнего.   -  person Rinat Veliakhmedov    schedule 16.06.2020
comment
@RinatVeliakhmedov cppreference не является стандартом. Undefined не является незаконным.   -  person Thomas Sablik    schedule 16.06.2020
comment
я бы сказал нет, но мне любопытен хороший ответ, потому что мне трудно найти какую-либо информацию о том, что на самом деле можно сделать с недействительными итераторами   -  person 463035818_is_not_a_number    schedule 16.06.2020
comment
@RinatVeliakhmedov ваша предпосылка заключается в том, что последнее не может быть достигнуто с первого, чтобы прийти к выводу, что последнее не может быть достигнуто с первого, это спорный аргумент   -  person 463035818_is_not_a_number    schedule 16.06.2020
comment
@ThomasSablik 23.4.2 Предварительные условия: последний доступен из первого или InputIterator соответствует требованиям Cpp17RandomAccessIterator и первый доступен из последнего.   -  person Rinat Veliakhmedov    schedule 16.06.2020
comment
Итераторы предназначены для итерации, а не для долговременного хранения косвенных обращений. Если вы используете индексы для начала, вам не нужен перевод или промежуточные звенья, и вам не нужно беспокоиться о недействительности.   -  person molbdnilo    schedule 16.06.2020
comment
@ThomasSablik Я пояснил, что я имею в виду под юридическим. Я не уверен, почему недействительные итераторы будут недоступны.   -  person tsuki    schedule 16.06.2020
comment
@molbdnilo Эта структура данных является промежуточным результатом алгоритмов, которые работают с итераторами и действительно выполняют итерацию. Я полагал, что хранение индексов будет расточительным, и мне нужно только скопировать эти структуры для надежной гарантии исключения.   -  person tsuki    schedule 16.06.2020
comment
Итак, это XY-проблема. Я бы сделал шаг назад и сосредоточился на исходной проблеме, а не на проблемах, вызванных предлагаемым решением этой проблемы.   -  person molbdnilo    schedule 16.06.2020
comment
@tsuki Какие операции с вектором данных вы выполняете, которые делают итераторы недействительными?   -  person Ted Lyngmo    schedule 16.06.2020
comment
@TedLyngmo Перемещение вектора.   -  person tsuki    schedule 16.06.2020
comment
@tsuki Как вы думаете, почему это делает итераторы недействительными? Смотрите мой ответ.   -  person Ted Lyngmo    schedule 16.06.2020
comment
@TedLyngmo stackoverflow.com/a/11022447/2571986 утверждает, что перемещение делает итераторы недействительными, но действительно похоже, что std::swap другой случай.   -  person tsuki    schedule 16.06.2020
comment
@tsuki Смотрите мой обновленный ответ. Я утверждаю, что и перемещение, и обмен сохраняют действительность итератора.   -  person Ted Lyngmo    schedule 16.06.2020


Ответы (1)


Является ли поведение std::distance неопределенным при вызове пары итераторов std::vector, которые были признаны недействительными при перемещении vector?

Если итераторы действительны до перемещения, они останутся действительными и после перемещения, поэтому вам не нужно пересчитывать их с помощью std::distance.

(выделено мной ниже)

std::vector::vector

После перемещения конструкции контейнера ссылки, указатели и итераторы и итераторы (кроме конечного итератора) в other остаются действительными, но относятся к элементам, которые теперь находятся в *this.

Текущий стандарт делает эту гарантию с помощью общего заявления в [container.requirements.general /12], и рассматривается более прямая гарантия через LWG 2321.

[container.requirements.general/12] указывает, что

Если не указано иное (явно или путем определения функции в терминах других функций), вызов функции-члена контейнера или передача контейнера в качестве аргумента библиотечной функции не должны делать недействительными итераторы или изменять значения объектов внутри этого контейнера.

То же самое относится и к оператору присваивания перемещения, и это означает, что в соответствии со стандартом итераторы будут оставаться в силе после перемещения.

Текущая формулировка в LWG 2321 дает намек на то, как может выглядеть новый параграф в стандарте. если рабочая группа библиотеки завершит это - что кажется сложным. LWG 2321 был открыт еще в 2013 году.

никакой конструктор перемещения (или оператор присваивания перемещения, когда allocator_traits<allocator_type>::propagate_on_container_move_assignment::value равен true) контейнера (кроме array) не делает недействительными любые ссылки, указатели или итераторы, относящиеся к элементам исходного контейнера. [Примечание. Итератор end() не ссылается ни на один элемент, поэтому он может быть признан недействительным. — конец примечания]

Если это слишком расплывчато, вы можете использовать

[container.requirements.general/11.6]

ни одна из функций swap() не делает недействительными любые ссылки, указатели и или итераторы, относящиеся к заменяемым элементам контейнеров. [Примечание: итератор end() не ссылается ни на один элемент, поэтому он может быть признан недействительным. — примечание в конце]

Если итераторы действительны до swap, они действительны после swap.

Вот пример класса, использующего гарантию, данную для swap:

#include <vector>

class Foo {
    std::vector<int> data{};
    std::vector<decltype(data)::iterator> dits{};

public:
    Foo() = default;

    Foo(const Foo&) = delete;    // here, dits would need to be calculated

    // A move constructor guaranteed to preserve iterator validity.
    Foo(Foo&& rhs) noexcept {
        data.swap(rhs.data);
        dits.swap(rhs.dits);
    }

    Foo& operator=(const Foo&) = delete;

    // A move assignment operator guaranteed to preserve iterator validity.
    Foo& operator=(Foo&& rhs) noexcept {
        data.swap(rhs.data);
        dits.swap(rhs.dits);
        return *this;
    }

    ~Foo() = default;
};
person Ted Lyngmo    schedule 16.06.2020
comment
Вопрос заключается в вычислении расстояния между двумя недействительными итераторами. Недействительное состояние тогда не подлежит сомнению. - person Nicolas Dusart; 16.06.2020
comment
@NicolasDusart Да, но когда я прочитал вопрос, я интерпретировал его так, как будто OP считает, что итераторы недействительны только из-за перемещения, поэтому я хотел прояснить, что до тех пор, пока итераторы действительны, вы может поменять целевой контейнер, и итераторы по-прежнему будут действительны. - person Ted Lyngmo; 16.06.2020
comment
@NicolasDusart ... и теперь OP подтвердил мое подозрение, и я прочитал об этом немного больше и утверждаю, что даже перемещение сохраняет действительность итераторов. - person Ted Lyngmo; 16.06.2020
comment
@NicolasDusart Нет, я не это имел в виду :-) Я подумал, что тебе любопытно, поэтому отметил тебя. - person Ted Lyngmo; 16.06.2020