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

С++ вектор объектов против вектора указателей на объекты

Я пишу приложение, используя openFrameworks, но мой вопрос не специфичен только для oF; скорее, это общий вопрос о векторах C++ в целом.

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

// instantiate object dynamically, do something, then append to vector
vector<ofVideoPlayer> videos;
ofVideoPlayer *video = new ofVideoPlayer;
video->loadMovie(filename);
videos.push_back(*video);

// access object in vector and do something; compiles but does not work properly
// without going into specific openFrameworks details, the problem was that the video would
// not draw to screen
videos.at(0)->draw();

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

vector<ofVideoPlayer*> videos;
ofVideoPlayer * video = new ofVideoPlayer;
video->loadMovie(filename);
videos.push_back(video);
// now dereference pointer to object and call draw
videos.at(0)->draw();

Я выделял память для объектов динамически, т.е. ofVideoPlayer = new ofVideoPlayer;

Мой вопрос прост: зачем использовать вектор указателей, и когда вы создадите вектор объектов по сравнению с вектором указателей на эти объекты?

4b9b3361

Ответ 1

Мой вопрос прост: почему с помощью вектора указателей сработало, и когда бы вы создали вектор объектов против вектора указателей на эти объекты?

std::vector похож на необработанный массив с новыми и перераспределенными массивами, когда вы пытаетесь вставить больше элементов, чем его текущий размер.

Таким образом, если он содержит указатели A, это как если бы вы манипулировали массивом A*. Когда ему нужно изменить размер (вы push_back() элемента, пока он уже заполнен до его текущей емкости), он создаст другой массив A* и скопирует в массив A* из предыдущего вектора.

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

Увидеть разницу? Объекты A в std::vector<A> могут изменить адрес, если вы выполняете некоторые манипуляции, требующие изменения размера внутреннего массива. Вот откуда большинство проблем с содержащимися объектами в std::vector.

Чтобы использовать std::vector без таких проблем, выделите достаточно большой массив с самого начала. Ключевое слово здесь - "емкость". Емкость std::vector - это реальный размер буфера памяти, в который он помещает объекты. Итак, для настройки емкости у вас есть два варианта:

1) размер вашего std::vector при построении, чтобы построить все объекты с самого начала, с максимальным количеством объектов - которые будут вызывать конструкторы каждого объекта.

2) после того, как std::vector создан (но в нем ничего нет), используйте его функцию reserve(): вектор будет выделять достаточно большой буфер (вы указываете максимальный размер вектора). Вектор установит емкость. Если вы используете объекты push_back() в этом векторе или resize() с ограничением размера, указанного в вызове reserve(), он никогда не будет перераспределять внутренний буфер, и ваши объекты не будут менять расположение в памяти, делая указатели на эти объекты всегда действительны (некоторые утверждения, чтобы проверить, что изменение емкости никогда не происходит, является отличной практикой).

Ответ 2

Что вам нужно знать о векторах в С++, так это то, что они должны использовать оператор копирования класса ваших объектов, чтобы иметь возможность вводить их в вектор. Если у вас было выделение памяти в этих объектах, которое было автоматически освобождено при вызове деструктора, это могло бы объяснить ваши проблемы: ваш объект был скопирован в вектор, а затем уничтожен.

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

Эта проблема не возникает, если вы используете указатели, потому что вы управляете жизнью ваших элементов с помощью new/destroy, а векторные функции копируют только указатель на ваши элементы.

Ответ 3

Если вы выделяете память для объектов с помощью new, вы выделяете ее в кучу. В этом случае вы должны использовать указатели. Однако в С++ соглашение обычно создает все объекты в стеке и передает копии этих объектов, вместо того, чтобы передавать указатели на объекты в куче.

Почему это лучше? Это потому, что С++ не содержит сборку мусора, поэтому память для объектов в куче не будет возвращена, если вы специально не delete объект. Однако объекты в стеке всегда уничтожаются, когда они покидают область. Если вы создаете объекты в стеке вместо кучи, вы минимизируете риск утечки памяти.

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

Если ваши объекты слишком велики, чтобы их можно было эффективно копировать, допустимо использовать указатели. Тем не менее, чтобы избежать утечек памяти, вы должны использовать интеллектуальные указатели подсчета ссылок (либо С++ 0x auto_ptr, либо один указатель библиотеки Boost).

Ответ 4

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

Если вы сделаете указатель vector указателем, используйте умный указатель, чтобы упростить код и свести к минимуму риск утечек.

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

Ответ 5

Обычно я не храню классы непосредственно в std::vector. Причина проста: вы не знаете, получен ли класс или нет.

например:.

В заголовках:

class base
{
public:
  virtual base * clone() { new base(*this); };
  virtual ~base(){};
};
class derived : public base
{
public:
  virtual base * clone() { new derived(*this); };
};
void some_code(void);
void work_on_some_class( base &_arg );

В источнике:

void some_code(void)
{
  ...
  derived instance;
  work_on_some_class(derived instance);
  ...
}

void work_on_some_class( base &_arg )
{
  vector<base> store;
  ...
  store.push_back(*_arg.clone());
  // Issue!
  // get derived * from clone -> the size of the object would greater than size of base
}

Поэтому я предпочитаю использовать shared_ptr:

void work_on_some_class( base &_arg )
{
  vector<shared_ptr<base> > store;
  ...
  store.push_back(_arg.clone());
  // no issue :)
}

Ответ 6

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