C ++ вектор объектов против вектора указателей на объекты - PullRequest
33 голосов
/ 08 июля 2011

Я пишу приложение, используя 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;

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

Ответы [ 6 ]

26 голосов
/ 08 июля 2011

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

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

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

19 голосов
/ 08 июля 2011

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

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(), он никогда не перераспределит внутренний буфер, и ваши объекты не изменят местоположение в памяти, делая указатели наэти объекты всегда действительны (некоторые утверждения, чтобы проверить, что изменение емкости никогда не происходит, является отличной практикой).

7 голосов
/ 08 июля 2011

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

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

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

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

4 голосов
/ 08 июля 2011

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

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

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

3 голосов
/ 08 июля 2011

Обычно я не храню классы непосредственно в 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 :)
}
2 голосов
/ 26 июля 2013

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...