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

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

Я пытаюсь улучшить свои навыки на С++, написав образец рендеринга программ. Он берет объекты, состоящие из точек в трехмерном пространстве, и сопоставляет их с окном просмотра 2d и рисует круги различного размера для каждой точки зрения. Что лучше:

class World{
    vector<ObjectBaseClass> object_list;
public:
    void generate(){
        object_list.clear();
        object_list.push_back(DerivedClass1());
        object_list.push_back(DerivedClass2());

или...

class World{
    vector<ObjectBaseClass*> object_list;
public:
    void generate(){
        object_list.clear();
        object_list.push_back(new DerivedClass1());
        object_list.push_back(new DerivedClass2());

?? Будет ли использовать указатели во втором примере для создания новых объектов, которые победят точку использования векторов, потому что векторы автоматически вызывают деструкторы DerivedClass в первом примере, но не во втором? Являются ли указатели на новые объекты необходимыми при использовании векторов, потому что они сами обрабатывают управление памятью, пока вы используете их методы доступа? Теперь скажем, что у меня есть другой метод в мире:

void drawfrom(Viewport& view){
    for (unsigned int i=0;i<object_list.size();++i){
        object_list.at(i).draw(view);
    }
}

При вызове this будет выполняться метод draw для каждого объекта в списке мира. Скажем, я хочу, чтобы производные классы могли иметь свои собственные версии draw(). Будет ли список иметь указатели тогда, чтобы использовать селектор метода (- > )?

4b9b3361

Ответ 1

Вы не получите то, что хотите с помощью этого кода

class World{
    vector<ObjectBaseClass> object_list;
public:
    void generate(){
        object_list.clear();
        object_list.push_back(DerivedClass1());
        object_list.push_back(DerivedClass2());

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

Чтобы сделать работу полиморфизма, вам нужно использовать какие-то указатели. Вероятно, есть некоторые интеллектуальные указатели или ссылки в boost или других библиотеках, которые можно использовать и сделать код намного безопаснее второго предлагаемого решения.

Ответ 2

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

Использование shared_ptr

Использование shared_ptr может объявить ваш вектор следующим образом:

std::vector< boost::shared_ptr< ObjectBase > > object_list;

И используйте его следующим образом:

typedef std::vector< boost::shared_ptr< ObjectBase > >::iterator ObjectIterator;

for ( ObjectIterator it = object_list.begin(); it != object_list.end(); it++ )
    (*it)->draw(view);

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

Примечание о С++ 11: В С++ 11 shared_ptr стал частью стандарта как std::shared_ptr, поэтому Boost больше не требуется для этого подхода. Однако, если вам действительно не требуется совместное владение, рекомендуется использовать std::unique_ptr, который был недавно введен в С++ 11.

Используя ptr_vector

Используя ptr_vector, вы сделаете это следующим образом:

boost::ptr_vector< ObjectBase > object_list;

И используйте его следующим образом:

typedef boost::ptr_vector< ObjectBase >::iterator ObjectIterator;

for ( ObjectIterator it = object_list.begin(); it != object_list.end(); it++ )
    (*it)->draw(view);

Это снова будет использоваться как обычный вектор указателей, но на этот раз ptr_vector управляет временем жизни ваших объектов. Разница с первым подходом заключается в том, что здесь ваши объекты уничтожаются, когда вектор уничтожается, а выше они могут проживать дольше, чем контейнер, если существуют другие ссылки shared_ptr.

Использование reference_wrapper

Используя reference_wrapper, вы объявите его следующим образом:

std::vector< boost::reference_wrapper< ObjectBase > > object_list;

И затем используйте его следующим образом:

typedef std::vector< boost::reference_wrapper< ObjectBase > >::iterator 
    ObjectIterator;

for ( ObjectIterator it = object_list.begin(); it != object_list.end(); it++ )
    it->draw(view);

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

Примечание о С++ 11: reference_wrapper также стандартизовано в С++ 11 и теперь можно использовать как std::reference_wrapper без Boost.

Как указано в ответе Maciej H, ваш первый подход приводит к разбиению объектов. В общем случае вы можете посмотреть iterators при использовании контейнеров.

Ответ 3

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

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

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

Ответ 4

Ну, это зависит от того, что вы пытаетесь сделать с вашим вектором.

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