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

Должен ли end() быть постоянным в STL-карте/множестве?

§23.1.2.8 в стандарте заявляет, что операции вставки/удаления на множестве/карте не будут аннулировать любые итераторы для этих объектов (кроме итераторов, указывающих на удаленный элемент).

Теперь рассмотрим следующую ситуацию: вы хотите реализовать граф с уникально нумерованными узлами, где каждый node имеет фиксированное число (скажем, 4) соседей. Воспользовавшись приведенным выше правилом, вы делаете это так:

class Node {
    private:
        // iterators to neighboring nodes
        std::map<int, Node>::iterator neighbors[4];
        friend class Graph;
};

class Graph {
    private:
        std::map<int, Node> nodes;
};

( РЕДАКТИРОВАТЬ: Это не так буквально из-за неполноты Node в строке 4 (см. ответы/комментарии), но в любом случае по этим строкам)

Это хорошо, потому что таким образом вы можете вставлять и удалять узлы без аннулирования согласованности структуры (при условии, что вы отслеживаете удаления и удаляете удаленный итератор из каждого массива node).

Но скажем, вы также захотите сохранить "недопустимое" или "несуществующее" значение соседа. Не волнуйтесь, мы можем просто использовать nodes.end()... или можем ли мы? Есть ли какая-то гарантия того, что nodes.end() в 8 утра будет таким же, как nodes.end() в 10 вечера после zillion вставки/удаления? То есть, могу ли я безопасно == сравнить итератор, полученный как параметр, в nodes.end() в некотором методе Graph?

А если нет, будет ли это работать?

class Graph {
    private:
        std::map<int, Node> nodes;
        std::map<int, Node>::iterator _INVALID;
    public:
        Graph() { _INVALID = nodes.end(); }
};

То есть, могу ли я сохранить nodes.end() в переменной при конструировании, а затем использовать эту переменную всякий раз, когда я хочу установить соседа на недопустимое состояние или сравнить его с параметром в методе? Или возможно, что где-то по строке действительный итератор, указывающий на существующий объект, будет сравниваться с _INVALID?

И если это тоже не сработает, что я могу сделать, чтобы оставить место для недопустимого значения соседа?

4b9b3361

Ответ 1

Вы пишете (выделение мной):

§23.1.2.8 в стандарте заявляет, что операции вставки/удаления на множестве/карте не будут аннулировать любые итераторы для этих объектов (кроме итераторов, указывающих на удаленный элемент).

Собственно, текст 23.1.2/8 немного отличается (опять же, сделанный мной):

Элементы вставки не должны влиять на действительность итераторов и ссылок на контейнер, а члены стирания делают недействительными только итераторы и ссылки на стертые элементы.

Я читал это как: если у вас есть карта и каким-то образом получить итератор на этой карте (опять же: он не говорит объекту на карте), этот итератор останется действительным, несмотря на вставку и удаление элементов. Предполагая, что std::map<K,V>::end() получает "итератор в карту", ​​он не должен быть аннулирован при вставке/удалении.

Это, конечно, оставляет вопрос, означает ли "недействительный" означает, что он всегда будет иметь одинаковое значение. Мое личное предположение заключается в том, что это не указано. Однако для того, чтобы фраза "недействительная" имела смысл, все результаты std::map<K,V>::end() для одной и той же карты всегда должны сравниваться одинаково даже перед лицом вставки/удаления:

my_map_t::iterator old_end = my_map.end();
// wildly change my_map
assert( old_end == my_map.end() ); 

Моя интерпретация заключается в том, что если old_end остается "действительным" во всех изменениях карты (в качестве стандартных обещаний), то это утверждение должно пройти.

Отказ от ответственности: я не носитель языка и очень усердно перевариваю этот страшный legaleze из Священного PDF. На самом деле я вообще избегаю этого, как чума.

О, и моя первая мысль также была: вопрос интересен из академического POV, но почему он просто не кладет ключи вместо итераторов?

Ответ 2

23.1/7 говорит, что end() возвращает итератор, который

- это значение конца для контейнера.

Во-первых, он подтверждает, что возвращаемый end() - это итератор. Во-вторых, в нем говорится, что итератор не указывает на определенный элемент. Поскольку удаление может приводить к недействительности итераторов, которые указывают где-то (удаляемому элементу), удаления не могут аннулировать end().

Ответ 3

Хорошо, что ничто, препятствующее реализации конкретной коллекции от end(), зависит от экземпляра коллекции и времени суток, при сравнении и такой работы. Это означает, что, возможно, значение end() может измениться, но сравнение old_end == end() должно все же дать true. (отредактируйте: хотя после прочтения комментария от j_random_hacker я сомневаюсь, что этот абзац сам оценивает true;-), а не повсеместно - см. обсуждение ниже)

Я также сомневаюсь, что вы можете использовать std::map<int,Node>::iterator в классе Node из-за неполного типа (хотя и не уверенного).

Кроме того, поскольку ваши узлы имеют уникальную нумерацию, вы можете использовать int для их ввода и зарезервировать некоторое недопустимое значение.

Ответ 4

Предполагая, что (1) карта реализована с красно-черным деревом (2), вы используете один и тот же экземпляр "после zillion insertions/deletions" - ответьте "Да".

Относительная импликация Я могу сказать, что все воплощения stl, которые я когда-либо знал, используют алгоритм дерева.

Ответ 5

Пара точек:

1) end() ссылается на элемент, который находится за концом контейнера. Он не изменяется, когда вставки или удаления изменяют контейнер, потому что он не указывает на элемент.

2) Я думаю, возможно, ваша идея хранения массива из 4 итераторов в Node может быть изменена, чтобы сделать всю проблему более разумной. Вы хотите добавить новый тип итератора к объекту Graph, который способен выполнять итерацию по одному соседу Node. Для реализации этого итератора необходимо будет получить доступ к членам карты, что, возможно, приведет вас к тому, что класс Graph расширит коллекцию карт. Поскольку класс Graph является расширенной std:: map, тогда язык изменяется, и вам больше не нужно хранить недействительный итератор, но вместо этого просто нужно написать алгоритм, чтобы определить, кто является "следующим соседом" на карте.

Ответ 6

Итераторы в (мульти) наборах и (мульти) картах не будут аннулированы во вставках и удалениях, и поэтому сравнение .end() с предыдущими сохраненными значениями .end() всегда даст true.

Возьмем в качестве примера реализацию GNU libstdС++, где .end() в картах возвращает значение по умолчанию Rb_tree_node

Из stl_tree.h:

  _M_initialize()
  {
    this->_M_header._M_color = _S_red;
    this->_M_header._M_parent = 0;
    this->_M_header._M_left = &this->_M_header;
    this->_M_header._M_right = &this->_M_header;
  }

Ответ 7

Я думаю, это понятно:

  • end() возвращает итератор к элементу, который передал конец.

  • Вставка/удаление не влияют на существующие итераторы, поэтому возвращаемые значения всегда действительны (если вы не попытаетесь удалить элемент, прошедший затем конец (но это все равно приведет к поведению undefined).

  • Таким образом, любой новый итератор, сгенерированный end() (будет отличаться, но) по сравнению с исходным с использованием оператора ==, вернет true.

  • Также любые промежуточные значения, сгенерированные с использованием оператора присваивания, имеют условие post, которое они сравнивают с оператором ==, а оператор == транзитивен для итераторов.

Итак, да, действительно, хранить итератор, возвращаемый end() (но только из-за гарантий с ассоциативными контейнерами, поэтому он недействителен для вектора и т.д.).

Помните, что итератор не обязательно является указателем. Он потенциально может быть объектом, где разработчик контейнера определил все операции над классом.

Ответ 8

Я считаю, что это полностью зависит от того, какой тип итератора используется.

В векторе end() - это один за конечным указателем, и он, очевидно, будет изменяться при вставке и удалении элементов.

В другом типе контейнера итератор end() может быть специальным значением, например NULL или построенным по умолчанию элементом. В этом случае он не изменяется, потому что он ничего не указывает. Вместо того, чтобы быть указательным способом, end() - это просто значение для сравнения.

Я считаю, что итераторы set и map - это второй тип, но я не знаю ничего, что требует их реализации таким образом.

Ответ 9

Стандарт С++ утверждает, что итераторы должны оставаться в силе. И это. Стандарт четко указывает, что в 23.1.2/8:

Элементы вставки не влияют на достоверность итераторов и ссылаются на на контейнер, а члены стирают недействительными только итераторы и ссылки к стираемым элементам.

И в 21.1/7:

end() возвращает iterator, который является значением конца для контейнера.

Таким образом, будут выполняться итераторы old_end и new_end. Это означает, что мы могли бы получить --old_end (назовем его it1) и --new_end (назовем его it2), и это будут итераторы значения конца (из определения того, что возвращает end()), так как итератор ассоциативный контейнер имеет категорию двунаправленного итератора (согласно 23.1.2/6) и согласно определению операции --r (таблица 75).

Теперь it1 должен быть равен it2, так как он дает значение конца, которое является только одним (23.1.2/9). Затем из 24.1.3 следует, что: Условие, что a == b подразумевает ++ a == ++ b. И ++it1 и ++it2 будут давать итераторы old_end и new_end (из определения операции ++ r, таблица 74). Теперь мы получаем, что old_end и new_end должны быть равными.

Ответ 10

У меня был аналогичный вопрос в последнее время, но мне было интересно, может ли вызов end() для извлечения итератора для целей сравнения, возможно, иметь условия гонки.

В соответствии со стандартом два итератора считаются эквивалентными, если оба могут быть разыменованы и &*a == &*b , или если ни один из них не может быть разыменован. Поиск жирного выражения занял некоторое время и здесь очень уместен.

Поскольку std::map::iterator не может быть признан недействительным, если только элемент, на который он указывает, был удален, вам гарантировано, что два итератора, возвращенные end, независимо от состояния карты, когда они были получены, всегда будут сравните друг с другом как истинные.