C ++: Вектор объектов против вектора указателей на новые объекты? - PullRequest
26 голосов
/ 22 апреля 2010

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

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());

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

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

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

Ответы [ 4 ]

27 голосов
/ 23 апреля 2010

Поскольку вы явно заявляете, что хотите улучшить свой C ++, я рекомендую вам начать использовать 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 ссылающийся на него будет уничтожен.

Примечание о C ++ 11: В C ++ 11 shared_ptr стало частью стандарта как std::shared_ptr, поэтому Boost больше не требуется для этого подхода. Однако, если вам действительно не нужно совместное владение, рекомендуется использовать std::unique_ptr, который был недавно представлен в C ++ 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.

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

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

19 голосов
/ 22 апреля 2010

Вы не получите то, что хотите с этим кодом

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

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

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

13 голосов
/ 22 апреля 2010

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

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

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

2 голосов
/ 22 апреля 2010

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

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

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