2009-09-30 2 views
1

У меня есть стандартный вектор указателей.C++ std vector - аннулированный итератор вопрос

При каких обстоятельствах итератор в этот вектор становится недействительным?

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

for (It = Vec.begin(); It != Vec.end(); It++){ 
    GoToOtherCode((*It)); 
} 

function GoToOtherCode (ObjectType* Obj){ 
    delete Obj; 
} 

Должно ли это аннулировать Итератор? Мне не кажется, что этого не должно быть, но тогда я застрял с трудной проблемой для отладки! (Я боюсь своего обходного пути - для итерации через вектор с помощью integer-index. (Это отлично работает ... Я просто боюсь, почему вышеупомянутое вызывает проблемы с недействительностью).

Заранее спасибо для вашего времени.

Редактировать: Спасибо за совет. Общий консенсус в том, что приведенный выше код опасен, но это не приведет к аннулированию Iterator. Я считаю, что я столкнулся с ошибкой с отладчиком Visual Studio 2008, потому что после открытия проект на следующий день, эта недействительная проблема исчезла.Так что, как и во многих вещах на компьютерах, если ничего больше не работает, попробуйте сбросить эту вещь.

+0

вы получаете такую ​​«повод поверить»? Если это из книги, скажите нам, какой из них мы не рекомендуем никому другому. Если это было из ваших собственных экспериментов, тогда ваш метод был ошибочным и заслуживает более пристального взгляда. Трудно сказать, что произойдет в управляемом коде, поскольку он не имеет эквивалента 'delete'. –

+0

Пожалуйста, верните/отмените это, чтобы выяснить, интересуетесь ли вы стандартным C++ или расширенным языком Microsoft, реализованным в VS_. Нет такой вещи, как «C++ 8». – Novelocrat

ответ

8

Это не приведет к аннулированию итератора. Фактически вы можете удалить кучи выделенных объектов, которые принадлежат вектору, метод clear() не сделает этого для вас. Это довольно часто встречается:

for (It = Vec.begin(); It != Vec.end(); It++) 
    delete *It; 

Vec.clear(); 

Отлично, если вы не пытаетесь использовать то, что только что удалили.

+0

Хотя '++ It' было бы лучше ... – xtofl

+0

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

+0

Да, потому что вы не пытаетесь удалить его из вектора. – bobobobo

1

Это не должно аннулировать ваш итератор - но ...

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

Вектор сам по себе должен быть прекрасным - он просто укажет на множество мест, которые больше не действительны.

3

Это не делает недействительным итератор, но сам указатель указывает на теперь удаленный объект, поэтому вам действительно нужно что-то сделать, чтобы убедиться, что ваш код не срабатывает на теперь оборванном указателе. Например, вы можете установить указатель на нулевое значение или использовать erase, чтобы удалить удаленный элемент из вектора.

2

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

for(It i = vec.begin(); i != vec.end(); i++) 
{ 
    GoToOtherCode(*i); 
} 

void GoToOtherCode (ObjectType *& pref) 
{ 
    // This *should* set the iterator's copy of the pointer to null 
    delete pref; 
    pref = 0; 
} 

Тогда вы можете проверить наличие нулей в своем векторе. Предупреждение: непроверенный код.

+0

Не совсем корректно, итератор фактически был бы указателем на указатель (в данном случае), поэтому разыменование его все равно даст вам указатель. –

+0

@John, но когда тип назначения (в данном случае параметр функции) является ссылкой, передается ссылка на значение (в данном случае указатель). –

+0

Я не уверен, что вы предлагаете. Его оригинальный код будет работать нормально, поскольку вектор будет объявлен как std :: vector Vec, и поэтому тип, возвращаемый разыменованием итератора, будет ObjectType *. –

1

Одна вещь, которая может вызвать некоторую странность (а может быть, и коррупцию), заключается в удалении указателя на экземпляр класса, который происходит от одного, у которого нет деструктора virtual. Я не знаю, видел ли кто-нибудь это коррупцию или нет, но я могу представить, что это вызывает проблемы.Я думаю о чем-то вроде:

//----- base.h ----- 
// nothing declared as virtual here!! 
class Base { 
public: 
    Base(); 
    ~Base(); 
}; 
Base* getPointer(); 

//----- derived.cpp ----- 
class Derived: public Base { 
public: 
    Derived(); 
    ~Derived(); 
}; 
Base* getPointer() { 
    return new Derived(); 
} 

//----- main.cpp ----- 
#include "base.h" 
#include <vector> 
int main() { 
    std::vector<Base*> v; 
    v.push_back(getPointer()); 
    for (std::vector<Base*>::iterator i=v.begin(); i!=v.end(); ++i) { 
    delete *i; // Derived::~Derived() is not invoked here 
    } 
    v.clear(); 
    return 0; 
} 

Это, вероятно, не так, но я подумал, что упомянул об этом на всякий случай.

+1

Существует потенциальная утечка памяти (а не повреждение), если у производного класса есть члены, потому что его деструктор не будет вызван. В вашем текущем примере ничего «плохого» не произойдет, потому что производный класс пуст, но да, класс, который должен быть базой для других, должен объявить виртуальный деструктор. –

+0

@ Джон. Это то, что я подозревал. Я не мог придумать сценарий, который мог бы вызвать коррупцию. Я полагал, что упомянул об этом, поскольку, как известно, причиной появления печали стали невидимые производные классы в вектор указателей на базу. –

0

Как уже упоминалось, при вызове delete на итераторе вы удаляете данные, на которые указывает, но не указатель. Вы должны убедиться, что вы сами указали указатель на NULL или используете метод erase для вектора, чтобы избавиться от указателя.

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

0

Итератор не будет признан недействительным (если вы не измените порядок элементов).

Но у меня есть два других suggerstions/наилучшая практика:

  1. Использование Преинкремент istead пост-инкремент перебирать ваш вектор. Post-increment всегда создает временную копию вашего текущего элемента, что может быть дорогостоящим! (ну для указателей это не имеет значения ..., но вы всегда должны использовать pre-inc!).
  2. Не используйте вектор простых указателей. Используйте вектор ссылок, считающих интеллектуальные указатели. shared_ptr будет включен в C++ 0x, и почти все текущие реализации C++ (VC8, gcc, intel, ...) уже имеют его (он также доступен для повышения коэффициента усиления). Умный указатель владеет объектом и, следовательно, наблюдает за его жизнью (удаляет его, если он больше не нужен. Поэтому вам не нужно заботиться ...

Влияние производительности очень минимальное (всего один уровень вызова) ..

typedef vector<shared_ptr<ObjectType> > MyObjVector; 
MyObjVector Vec; 

for (It = Vec.begin(); It != Vec.end(); ++It) 
{ 
    GoToOtherCode(*It); 
} 

function GoToOtherCode (shared_ptr<ObjectType>& PObj) 
{ 
    // no delete needed! 
} 
0

фактически итератор аннулирует когда вектор перераспределение происходит. при инициализации вектора, он фактически сохраняет за собой некоторую память изначально, но когда эта память все израсходована вектором, весь вектор перераспределяется int memory и тем самым аннулировать все итераторы.