Удаление элемента из одного набора путем вызова erase (итератор) для другого набора. Это нормальное поведение? - PullRequest
0 голосов
/ 08 декабря 2010

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

Итак, давайте установим сцену:

У меня есть набор объектов, которые яразделены на два набора STL по логическим причинам (то есть, они являются непересекающимися наборами).

std::set<Obj> set_a;
std::set<Obj> set_b;

Set_a содержит нужные мне объекты.Если этот объект не найден в set_a, создается пустой объект и вставляется в set_b.Затем я выполняю некоторые вычисления для требуемого объекта, и, если выполняется определенное условие, я удаляю его.

std::set<Obj>::iterator it = set_a.find(o);
std::set<Obj>::iterator end = set_a.end();

if (it == end) {
    it = set_b.lower_bound();
    end = set_b.end();

    if (it == end || *it != o) {
        it = set_b.insert(it, o);
    }
}

// do calculations with it
if (/*condition is met*/) {
    // erase it
}

Поэтому мне было интересно, как бы я удалил этот объект.Поскольку у меня есть итератор, я подумал об удалении, используя его напрямую.Но что происходит, когда вы используете стирание с итератором, «указывающим» на объект в другом наборе?В документации, которую я использую, ничего нет (http://www.cplusplus.com/reference/stl/set/erase/), поэтому я выполнил следующий тест.

#include <iostream>
#include <iterator>
#include <algorithm>
#include <set>
#include <sstream>

// streams
using std::cout;
using std::ostream_iterator;
using std::ostringstream;
// data structures
using std::set;
// algorithms
using std::copy;

int main() {
    set<int> s, s2;
    s.insert(1);
    s.insert(2);
    s.insert(4);

    cout << "Initial set\n";
    // print set elements
    copy(s.begin(), s.end(), ostream_iterator<int> (cout, " "));
    cout << "\n";

    set<int>::iterator s_it = s.lower_bound(3);

    if (s_it == s.end() || *s_it != 3) {
        s_it = s.insert(s_it, 3);
    }

    cout << "Set after insertion\n";
    // print set elements
    copy(s.begin(), s.end(), ostream_iterator<int> (cout, " "));
    cout << "\n";

    // erase element from another set
    s2.erase(s_it);

    cout << "Set after erasure\n";
    // print set elements
    copy(s.begin(), s.end(), ostream_iterator<int> (cout, " "));
    cout << "\n";

    return 0;
}

Результатом этого теста является следующий:

Initial set
1 2 4 
Set after insertion
1 2 3 4 
Set after erasure
1 2 4 

Мне показалось очень страннымчто элемент из s можно удалить, вызвав метод из s2 , поэтому я выполнил свою программу с valgrind. Никаких ошибок там нет. Я думаю, что на самом деле я бы выиграл от такого поведения, так как мне не нужнопроверьте, в каком наборе содержится элемент, но мне интересно, нормальное ли это поведение или нет.

Спасибо!

Ответы [ 3 ]

8 голосов
/ 09 декабря 2010

Это может работать из-за того, что ваш поставщик реализует std::set<T> и std::set<T>::iterator, но это не гарантируется.

Стандартный раздел 23.1.2 В параграфе 7 и таблице 69 говорится, что выражение a.erase(q) действителен, когда a является объектом типа класса ассоциативного контейнера (set является ассоциативным контейнером) и "q обозначает действительный разыменовываемый итератор для a".

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

2 голосов
/ 09 декабря 2010

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

Итак, то, что вы видите, это чистая удача и случайность, которые работают в реализации STL вашего компилятора и на текущей фазе луны.Я бы не стал делать ставку на то, чтобы это работало где-то или когда-либо еще.

0 голосов
/ 09 декабря 2010

Как насчет использования локального указателя на std :: set ?

std::set<Obj> set_a;
std::set<Obj> set_b;
std::set<Obj>* current_set = &set_a;

std::set<Obj>::iterator it = current_set->find(o);
std::set<Obj>::iterator end = current_set->end();

if (it == end) {
    current_set = &set_b;
    it = current_set->lower_bound();
    end = current_set->end();

    if (it == end || *it != o) {
        it = current_set->insert(it, o);
    }
}

// do calculations with it
if (/* condition met */) {
   current_set->erase(it);
}
...