Я стремлюсь улучшить свои навыки C++ путем записи демонстрационного рендерера программного обеспечения. Это берет объекты, состоящие из точек в 3-м пространстве, и отображает их на 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);
}
}
При вызове это выполнит метод ничьей для каждого объекта в мировом списке. Скажем, я хочу, чтобы производные классы смогли иметь свои собственные версии ничьей (). Список должен был бы иметь указатели затем для использования селектора метода (->)?
С помощью этого кода вы не получите того, что хотите.
class World{
vector<ObjectBaseClass> object_list;
public:
void generate(){
object_list.clear();
object_list.push_back(DerivedClass1());
object_list.push_back(DerivedClass2());
То, что должно произойти, называется нарезкой объекта. Вы получите вектор ObjectBaseClass.
Чтобы полиморфизм работал, вы должны использовать какие-то указатели. Вероятно, есть некоторые умные указатели или ссылки в boost или других библиотеках, которые можно использовать и сделать код намного безопаснее, чем второе предложенное решение.
Поскольку вы явно указываете если вы хотите улучшить свой 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);
Обратите внимание, что вам не нужно разыменовать итератор сначала, как в вышеупомянутых подходах. Однако это работает только в том случае, если время жизни ваших объектов управляется где-то еще и гарантированно больше, чем у вектора
.
Примечание о C ++ 11: reference_wrapper
также стандартизирован в C ++ 11 и теперь может использоваться как std :: reference_wrapper
без Boost.
Как указано в ответе Maciej H , ваш первый подход приводит к нарезке объекта . В общем, вы можете изучить итераторы при использовании контейнеров.
Ну, это зависит от того, что вы пытаетесь сделать со своим вектором.
Если вы не используете указатели, то в вектор помещается копия переданного вами объекта. Если это простой объект и / или вы не хотите беспокоиться об отслеживании хранилища для них, это может быть именно то, что вам нужно. Если это что-то сложное или требует очень много времени для создания и разрушения, вы можете предпочесть выполнять эту работу только один раз и передавать указатели в вектор.
Что касается вашего первого вопроса, это обычно предпочитают использовать автоматически выделяемые объекты, а не динамически выделяемые объекты (другими словами, не для хранения указателей), пока для рассматриваемого типа копирование и присваивание возможно и не слишком дорого.
Если объекты не могут быть скопированы или назначены, вы все равно не сможете поместить их прямо в std :: vector
, поэтому вопрос спорный. Если операции копирования и / или присваивания являются дорогостоящими (например, объект хранит большой объем данных), вы можете захотеть сохранить указатели из соображений эффективности. В противном случае, как правило, лучше не хранить указатели именно по той причине, которую вы упомянули (автоматическое освобождение)
Что касается вашего второго вопроса, да, это еще одна веская причина для хранения указателей. Динамическая отправка (вызовы виртуальных методов) работает только с указателями и ссылками (и вы не можете хранить ссылки в std :: vector
). Если вам нужно хранить объекты нескольких полиморфных типов в одном векторе, вы должны хранить указатели, чтобы избежать разделения.