Арифметика c на недействительных итераторах - PullRequest
3 голосов
/ 16 июня 2020

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

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

1 Ответ

1 голос
/ 16 июня 2020

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

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

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

std :: vector :: vector

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

Текущий стандарт делает эту гарантию через выражение blanket в [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]

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

Если итераторы действительны до you 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;
};
...