С++ вектор указателей объектов класса

Что я пытаюсь сделать, так это создать два вектора объектов, где некоторые объекты вводятся в оба списка, а некоторые просто вводятся в один. Первая проблема, которую я обнаружил, заключалась в том, что когда я использовал push_back() для добавления объекта в оба списка, объект копировался, поэтому, когда я изменил его из одного списка, объект не изменился в другом. Чтобы обойти это, я попытался создать список указателей на объекты в качестве одного из списков. Однако, когда я позже обратился к указателю, данные оказались поврежденными, все значения члена данных были неправильными. Вот несколько фрагментов моего кода:

Определение векторов:

vector<AbsorbMesh> meshList;
vector<AbsorbMesh*> absorbList;

... Добавление объекта к обоим:

AbsorbMesh nurbsMesh = nurbs.CreateMesh(uStride, vStride);

// Add to the absorption list
absorbList.push_back(&nurbsMesh);
// Store the mesh in the scene list
meshList.push_back(nurbsMesh);

Доступ к объекту:

if (absorbList.size() > 0)
{
float receivedPower = absorbList[0]->receivedPower;
}

Что я делаю не так?


person Nigel    schedule 25.11.2009    source источник
comment
почему meshlist хранит копию реальных объектов? Если вы хотите сохранить один и тот же объект в обоих векторах, тогда оба должны быть указателями   -  person Naveen    schedule 25.11.2009
comment
Какова ваша реальная цель здесь (кроме 2 полупохожих списков)? Хранение указателей в контейнерах STL обычно приводит к плохим вещам.   -  person Adam W    schedule 25.11.2009
comment
@ Адам Почему это? Только потому, что указатели не могут быть освобождены? На мой взгляд, хорошо хранить указатели (или интеллектуальные указатели) внутри контейнеров STL, поскольку таким образом вы не платите за создание копии объектов, например, при изменении размера контейнера.   -  person Edison Gustavo Muenz    schedule 25.11.2009
comment
@Edison: Умные указатели, с которыми я полностью согласен. Стандартные указатели, как правило, приводят к проблемам, поскольку вам приходится самостоятельно обрабатывать освобождение и предотвращать нулевые указатели во всех операциях. Со всеми доступными библиотеками кажется глупым хранить стандартные указатели в контейнерах STL. Если вы беспокоитесь о стоимости изменения размера, вы устанавливаете слишком низкую емкость.   -  person Adam W    schedule 25.11.2009
comment
Нет ничего плохого в хранении указателей в контейнерах. Если у вас приличный дизайн, должно быть четкое понимание того, какой объект «владеет» памятью и отвечает за ее очистку. Точный контроль над выделением/освобождением памяти — одна из основных причин использования C++. Если вы не хотите этого делать и вместо этого везде используете интеллектуальные указатели, используйте что-то вроде Java или C#.   -  person Tim    schedule 25.11.2009


Ответы (8)


Не хватает некоторых деталей, но это предположение.

nurbsMesh выходит за рамки между push_back и absorbList[0]->receivedPower.

Итак, теперь ваш вектор указателей содержит указатель на объект, которого больше не существует.

Попробуйте добавить конструктор копирования в свой класс AbsorbMesh и добавьте его в свой вектор следующим образом.

absorbList.push_back(new AbsorbMesh(nurbsMesh));
meshList.push_back(nurbsMesh);

не забудьте удалить объекты в absorbList, вот так

for(vector<AbsorbMesh*>::iterator it = absorbList.begin(); it != absorbList.end(); it++) {
    delete it;
  }

Или сохраните общий указатель в своем векторе вместо простого указателя. Boost имеет хорошую реализацию общего указателя, если вам интересно. См. документы здесь

Если вы хотите, чтобы обновления элементов в одном векторе изменяли объекты в другом векторе, вам необходимо хранить указатели в обоих векторах.

Используя ваши исходные требования (обновление элемента в одном векторе влияет на элементы в другом векторе, вот как я бы сделал это с общим указателем повышения. (ВНИМАНИЕ, непроверенный код)

vector<boost::shared_ptr<AbsorbMesh> > meshList;
vector<boost::shared_ptr<AbsorbMesh> > absorbList;

boost::shared_ptr<AbsorbMesh> nurb = new AbsorbMesh(nurbs.CreateMesh(uStride, vStride));

meshList.push_back(nurb);
absorbList.push_back(nurb);

...
...

if (absorbList.size() > 0)
{
    float receivedPower = absorbList[0].get()->receivedPower;
}
person Glen    schedule 25.11.2009
comment
@flyfishr64, эта ссылка не работает. Я предполагаю, что вы ссылаетесь на какой-то общий указатель? Обычно это хорошая идея, но если у OP возникают проблемы с областью действия, то обычно лучше начинать с простых вещей. - person Glen; 25.11.2009
comment
Вместо того, чтобы заниматься удалением ваших объектов, используйте RAII (получение ресурсов — это инициализация) и оберните ваши объекты умным указателем, прежде чем добавлять их в вектор. en.wikipedia.org/wiki/Resource_Acquisition_Is_Initialization - person Jeff Paquette; 25.11.2009
comment
Только что заметил, что ссылка не работает... как вообще сюда вставить ссылку в комментарий? - person Jeff Paquette; 25.11.2009
comment
@Glen: проблема с этим заключается в утверждении в вопросе: некоторые объекты вводятся в один список, а некоторые - в оба, если мы удалим только из одного, у нас будет утечка памяти. - person Naveen; 25.11.2009
comment
@ Навин, я пропустил этот момент. Я обновил свой ответ, чтобы показать, как это сделать с помощью boost::shared_ptr - person Glen; 25.11.2009

Вы сохраняете адрес объекта, выделенного в стеке. Объект nurbsMesh уничтожается, как только заканчивается ваш метод, выполняющий push_back(). Если вы попытаетесь получить доступ к этому указателю впоследствии, объект уже уничтожен и содержит мусор. Что вам нужно, так это сохранить объект, который остается даже после того, как функция выходит за рамки. Для этого выделяем память для объекта из кучи с помощью new. Но для каждого нового у вас должен быть соответствующий delete. Но в вашем случае у вас возникнут проблемы с его удалением, поскольку вы нажимаете один и тот же указатель на два вектора. Чтобы решить эту проблему, вам потребуется некоторый механизм подсчета ссылок на типы.

person Naveen    schedule 25.11.2009

Объект удаляется при попытке получить указатель из вектора.

Попробуйте сделать

vector.push_back(new Object);
person Staseg    schedule 25.11.2009

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

Таким образом, безопасный способ сделать это — хранить указатели в обоих векторах. Затем, конечно, вам нужно убедиться, что они удалены... но это C++ для вас.

person Thomas    schedule 25.11.2009
comment
Итак, если бы я сделал то, что предложил Адам: meshList.push_back(nurbsMesh); поглотитьList.push_back(meshList.back()); У меня были бы проблемы, если бы meshList перераспределил? Какие события могут вызвать это? Вставка, удаление... другие? - person Nigel; 25.11.2009
comment
С этим кодом проблем нет. Если ваши векторы хранят указатели, то сам указатель дублируется. Если они сами хранят объекты, они дублируются; но тогда, конечно, модификации одного не отображаются в другом. Я думаю, что любая модификация vector может привести к перераспределению. На практике уменьшение вектора не приводит к уменьшению его емкости (в libc++), но я не уверен, является ли это стандартизированным поведением. - person Thomas; 26.11.2009

absorbList.push_back(&nurbsMesh); неправильно

absorbList сохранить указатель на локальный объект. Когда nurbMesh уничтожается, вы не можете писать absorbList[0]->

person Alexey Malistov    schedule 25.11.2009

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

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

Чтобы исправить это, вам нужно сохранить указатель (возможно, «умный указатель») в обоих векторах (вместо того, чтобы один вектор содержал объект по значению).

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

person ChrisW    schedule 25.11.2009

В вашем примере есть несколько неправильных вещей

AbsorbMesh nurbsMesh = nurbs.CreateMesh(uStride, vStride);

Этот объект размещается в стеке. Это чисто локальный объект. Этот объект будет уничтожен, когда вы достигнете конца текущего блока, окруженного {}.

absorbList.push_back(&nurbsMesh);

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

meshList.push_back(nurbsMesh)

И это копирует совершенно новый объект в вектор.

В равной степени неправильно сначала помещать объект в вектор, а затем помещать указатель на объект в вектор с помощью absorbList.push_back( &meshList.back() ), потому что vector::push_back перераспределит весь вектор, сделав недействительными все указатели.

Возможно, вы сможете сначала создать все AbsorbMesh объекты, поместить их в вектор, а затем получить указатели на эти объекты в векторе. Пока вы не коснетесь вектора, все будет в порядке.

В качестве альтернативы создайте объекты в куче, используя new AbsorbMesh(), но не забудьте вызвать delete для каждого созданного таким образом указателя. В противном случае у вас есть утечка памяти.

Третье решение: избегайте проблем и используйте умные указатели, которые позаботятся об уничтожении объекта за вас.

person Sebastian    schedule 25.11.2009

Во-первых, как отмечают все остальные, вы не можете размещать объекты в стеке (т. е. иначе, чем с помощью new или чего-то подобного) и иметь их после выхода из области видимости.

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

В-третьих, auto_ptr<> просто не работает в STL-контейнерах, так как auto_ptrs нельзя скопировать.

Указатели на независимо выделенные объекты работают, но удалить их в нужное время сложно.

Что, вероятно, будет работать лучше всего, так это shared_ptr<>. Сделайте каждый вектор vector<shared_ptr<AbsorbMesh> >, распределите через new, и за небольшую потерю производительности вы избежите множества хлопот.

person David Thornley    schedule 25.11.2009