Функция стирания вектора удаляет неправильный объект

У меня есть вектор, объявленный как:

vector<Curve> workingSet;

Curve - это созданный мной класс, который содержит строку «name» и массив структур, динамически выделяемых конструктором.

У меня есть цикл, который должен удалить 2 (из 4) элементов из вектора.

vector<Curve>::iterator it = workingSet.begin();

    //remove tbi's children from the working set
    for (  ;  it != workingSet.end();){
        if (it->thisName == tbi.childAName ||it->thisName == tbi.childBName)
            it= workingSet.erase(it);
        else
            ++it;
        }

Когда отладчик достигает вызова .erase (it), я вижу, что итератор it указывает на кривую 2 в векторе. Это хорошо; Я хочу, чтобы кривая 2 была удалена из вектора.

Затем отладчик переводит меня в деструктор (у меня там есть точка останова), который предположительно должен разрушать кривую 2. Но когда я смотрю на часы «это», я вижу, что разрушаемая кривая - это кривая 4! Затем деструктор переходит к 'delete []' массиву в объекте, как требуется, и устанавливает указатель массива в NULL.

Когда отладчик возвращается к программе, выполнив вызов erase (), я вижу, что вектор 2 был удален из массива, а кривая 4 все еще там. Указатель на массив кривой 4 по-прежнему указывает на то же место, что и раньше, но память освобождена и массив содержит мусор.

Кто-нибудь может подсказать, почему кривую 4 путают?

N.b. (1) Существует конструктор копирования для класса кривой, который выполняет «глубокую» копию. N.b. (2) Класс / программа - это нечто большее, чем я упомянул здесь.

Кстати, указатели на массивы на кривых 2 и 4 указывают на разные места и содержат разные значения, в зависимости от отладчика.

Изменить: теперь я реализовал назначение копии. Теперь кажется, что правильный элемент стирается из вектора, но неправильный деструктор все еще вызывается! Однако, когда отладчик возвращается к массиву, кривая 4 остается нетронутой.


person Meir    schedule 30.10.2011    source источник
comment
Можете ли вы предоставить компилируемый минимальный образец, который показывает проблему? Ваше словесное описание не очень помогает. В любом случае: взгляните на Erase / Remove-Idiom. У нас есть масса вопросов, на которые даны ответы на SO.   -  person pmr    schedule 30.10.2011
comment
Еще одна вещь: вы упоминаете только копию ctor, вы также реализовали назначение копии?   -  person pmr    schedule 30.10.2011
comment
Я не выполнил задание копирования, сделаю это сейчас и вернусь к вам.   -  person Meir    schedule 30.10.2011
comment
Минимальный компилируемый пример, демонстрирующий описываемое вами поведение.   -  person pmr    schedule 30.10.2011
comment
Наблюдаю ту же проблему. Какой компилятор вы использовали?   -  person Wolf    schedule 13.05.2015
comment
Я тоже наблюдал это, используя gcc версии 5.4.0 20160609. vector :: erase уничтожает последний элемент в векторе независимо от того, какой элемент вы пытаетесь стереть.   -  person jhufford    schedule 25.07.2017


Ответы (2)


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

По крайней мере, так должно работать.

person Xeo    schedule 30.10.2011

Мне кажется, что vector :: erase не может использоваться с вектором локально созданных нетривиальных типов данных (объекты, не созданные в куче, собственное имя ускользает от меня прямо сейчас). У меня точно такое же поведение, как вы описываете, последний элемент разрушается дважды (особенно опасно, если у вашего объекта есть память, которая освобождается деструктором), и элемент, который вы удалили, никогда не разрушается. Я не знаю, почему это происходит, но это ловушка, которую нужно остерегаться.

Вот один из способов решить проблему:

#include <iostream>
#include <vector>
#include <memory>

using namespace std;

class MyClass 
{
   public:
    int *x;
    MyClass(int x)
    {
       cout << "MyClass Constructor " << x << endl;
       this->x = new int(x);
    }
    MyClass(const MyClass& obj)
    {
       this->x = new int(*obj.x);
       cout << "copy constructor " << *this->x << endl;
    }
    ~MyClass()
    {
       cout << "MyClass Destructor " << *x << endl;
       delete x;
    }
};
int main(int argc, char* argv[])
{
   // incorrect
   vector<MyClass> bad_vect;
   for(int i=0;i<3;i++){
      bad_vect.push_back(MyClass(i));
      // causes a bunch of copying to happen.
      // std::move does not seem to fix this either
      // but in the end everything gets constructed as we'd like
   }
   cout << " ---- " << endl;
   bad_vect.erase(bad_vect.begin() + 1); // we expect this to remove item with x = 1 and destruct it.
   // but it does NOT do that, it does remove the item with x=1 from the vector
   // but it destructs the last item in the vector, with x=2, clearly
   // not what we want. The destructor for object with x=1 never gets called
   // and the destructor for the last item gets called twice.
   // The first time with x=2 and since that memory is freed, the 2nd time
   // it prints garbage. Strangely the double-free doesn't crash the prog
   // but I wouldn't count on that all the time.
   // Seems some parts of STL have pitfalls with locally constructed objects
   cout << " ------------ " << endl;
   // below fixes this
   vector<unique_ptr<MyClass> >vect;
   for(int i=0;i<3;i++){
      unique_ptr<MyClass> ptr(new MyClass(i));
      vect.push_back( move( ptr )); // move is required since unique_ptr can only have one owner
      // or the single one-liner below
      //vect.push_back( move( unique_ptr<MyClass>(new MyClass(i)) ));
   }
   // the above prints out MyClass Constructor 0,1,2, etc
   vect.erase(vect.begin() + 1); // remove the 2nd element, ie with x=1
   // the above prints out MyClass Destructor 1, which is what we want

   for(auto& v : vect){
     cout << *(v->x) << endl;
   } // prints out 0 and 2
   return 0; // since we're using smart pointers, the destructors for the
           // remaining 2 objects are called. You could use regular pointers
           // but you would have to manually delete everything. shared_ptr
           // also works and you don't need the move(), but with a bit more overhead
}
person jhufford    schedule 25.07.2017