2010-03-22 3 views
7

Я хотел бы управлять кучей объектов классов, полученных из общего класса интерфейса в общем контейнере.Что было бы самым безопасным способом хранения объектов классов, полученных из общего интерфейса в общем контейнере?

Чтобы проиллюстрировать эту проблему, допустим, что я создаю игру, в которой будут находиться разные актеры. Назовем интерфейс IActor и выведем из него Enemy и Civilian.

Теперь, идея состоит в том, чтобы моя игра основной цикл будет в состоянии сделать это:

// somewhere during init 
std::vector<IActor> ActorList; 
Enemy EvilGuy; 
Civilian CoolGuy; 
ActorList.push_back(EvilGuy); 
ActorList.push_back(CoolGuy); 

и

// main loop 
while(!done) { 
    BOOST_FOREACH(IActor CurrentActor, ActorList) { 
     CurrentActor.Update(); 
     CurrentActor.Draw(); 
    } 
} 

... или что-то вдоль этих линий. Этот пример, очевидно, не сработает, но это в значительной степени причина, по которой я спрашиваю здесь.

Я хотел бы знать: что было бы лучшим, безопасным, самым высоким уровнем управления этими объектами в общем гетерогенном контейнере? Я знаю о различных подходах (Boost :: Any, void *, класс обработчика с boost :: shared_ptr, Boost.Pointer Container, dynamic_cast), но я не могу решить, каким будет путь сюда.

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

Помогите оценить!

ответ

3

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

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

Недостаток умных указателей заключается в том, что вы делите собственность.
Это не то, что вы действительно хотите. Вы хотите, чтобы собственность находилась в одном месте (в этом случае контейнер).

boost::ptr_vector<IActor> ActorList; 
ActorList.push_back(new Enemy()); 
ActorList.push_back(new Civilian()); 

и

std::for_each(ActorList.begin(), 
       ActorList.end(), 
       std::mem_fun_ref(&IActor::updateDraw)); 
+0

понравилось, как вы использовали for_each –

+0

Можете ли вы объяснить свой for_each и как использовать его с помощью BOOST_FOREACH? – Svenstaro

+0

std :: for_each (I1, I2, Action).Применяет действие ко всем значениям (в этом случае вызывает метод updateDraw) в диапазоне между I1 и I2 (не включая I2). Где I1, I2 - итераторы. См.: Http://www.sgi.com/tech/stl/for_each.html –

4

Моментальная реакция заключается в том, что вы должны хранить интеллектуальные указатели в контейнере и убедиться, что базовый класс определяет достаточно (чистые) виртуальные методы, которые вам никогда не нужны, чтобы dynamic_cast вернулся к производному классу.

10

Чтобы решить проблему, о которой упоминали, хотя вы идите в правильном направлении, но вы делаете это неправильно. Это то, что вам нужно будет сделать

  • Определить базовый класс (который вы уже делаете) с виртуальными функциями, которые будут переопределены в производных классов Enemy и Civilian в вашем случае.
  • Вам нужно выбрать подходящий контейнер с сохранением вашего объекта. Вы взяли std::vector<IActor>, который не является хорошим выбором, потому что
    • Во-первых, когда вы добавляете объекты к вектору, это приводит к разрезанию объектов. Это означает, что вместо всего объекта сохраняется только IActor часть Enemy или Civilian.
    • Во-вторых, вам нужно вызывать функции в зависимости от типа объекта (virtual functions), что может произойти только при использовании указателей.

Обе причины выше, указывают на то, что вам нужно использовать контейнер, который может содержать указатели, что-то вроде std::vector<IActor*>. Но лучшим вариантом было бы использовать container of smart pointers, который избавит вас от головных болей памяти.Вы можете использовать любой из смарт-указатели в зависимости от ваших потребностей (но не auto_ptr)

Это то, что ваш код будет выглядеть

// somewhere during init 
std::vector<some_smart_ptr<IActor> > ActorList; 
ActorList.push_back(some_smart_ptr(new Enemy())); 
ActorList.push_back(some_smart_ptr(new Civilian())); 

и

// main loop 
while(!done) 
{ 
    BOOST_FOREACH(some_smart_ptr<IActor> CurrentActor, ActorList) 
    { 
     CurrentActor->Update(); 
     CurrentActor->Draw(); 
    } 
} 

который довольно сильно похож на ваш исходный код, за исключением смарт-указателей, часть

+3

специально вы хотите смарт-указатель с семантикой копирования –

+0

YUP я согласен с этим –

+1

мне нравится ваш подход, поскольку он выглядит очень чистым в течение указателей на основе подхода. Однако, как указывали другие, что я хочу сделать, вероятно, лучше сделать с помощью Boost Pointer Containers, поскольку они действительно созданы для того, чего я хочу достичь. Я попробую ваш подход, если это не удастся. Или вы видите какую-либо причину * не * использовать контейнеры с указателем Boost? – Svenstaro

3

Если вы хотите, чтобы в контейнере были только собственные элементы, используйте контейнер указателя Boost: они предназначенный для этой работы. В противном случае используйте контейнер shared_ptr<IActor> (и, конечно же, используйте его правильно, что означает, что все, кому необходимо обмениваться собственностью, используют shared_ptr).

В обоих случаях убедитесь, что деструктор IActor является виртуальным.

void* требует от вас ручного управления памятью, так что это не так. Boost.Any является излишним, когда типы связаны по наследству - стандартный полиморфизм делает работу.

Нужно ли вам dynamic_cast или нет, является ортогональной проблемой - если пользователям контейнера нужен только интерфейс IActor, и вы либо (a) выполняете все функции интерфейса виртуальными, либо (b) -виртуальный интерфейс идиомы, тогда вам не нужен dynamic_cast. Если пользователи контейнера знают, что некоторые объекты IActor являются «действительно» гражданскими лицами и хотят использовать вещи, которые находятся в гражданском интерфейсе, но не IActor, тогда вам понадобятся касты (или редизайн).