Я хотел бы управлять набором объектов классов, полученных из общего интерфейсного класса в общем контейнере.
Для иллюстрирования проблемы скажем, я создаю игру, которая будет содержать различных агентов. Давайте назовем интерфейс 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();
}
}
... или что-то вдоль тех строк. Этот пример, очевидно, не будет работать, но это - в значительной степени причина, которую я спрашиваю здесь.
Я хотел бы знать: Каков был бы лучший, самый безопасный, высший уровень способ управлять теми объектами в общем неоднородном контейнере? Я знаю о множестве подходов (Повышение:: Любой, пусто*, класс обработчика с повышением:: shared_ptr, Повышение. Контейнер указателя, dynamic_cast), но я не могу решить, который был бы способом пойти сюда.
Также я хотел бы подчеркнуть, что я хочу избежать в максимально возможной степени ручного управления памятью или вложенных указателей.
Помогите многому ценившему :).
Как вы уже догадались, вам нужно хранить объекты как указатели.
Я предпочитаю использовать контейнеры указателей ускорения (а не обычные контейнеры интеллектуальных указателей).
Причина в том, что контейнер 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));
Решить проблему, о которой вы упомянули, хотя вы идете в правильном направлении, но делаете это неправильно. Это то, что вам нужно сделать
Enemy
и Civilian
в вашем случае . std :: vector
, что не является хорошим выбором, потому что
IActor
Enemy
или Civilian
. виртуальные функции
), что может произойти только при использовании указателей. Обе указанные выше причины указывают на то, что вам необходимо использовать контейнер, который может содержать указатели, например std :: vector
. Но лучшим выбором было бы использовать контейнер интеллектуальных указателей
, который избавит вас от головной боли, связанной с управлением памятью. Вы можете использовать любой из интеллектуальных указателей в зависимости от ваших потребностей (но не 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();
}
}
Что очень похоже на ваш исходный код за исключением части интеллектуальных указателей
Моя мгновенная реакция заключается в том, что вам следует хранить интеллектуальные указатели в контейнере и убедиться, что базовый класс определяет достаточно (чистых) виртуальных методов, которые вам никогда не понадобятся для dynamic_cast
обратно в производный класс.
Если вы хотите, чтобы контейнер исключительно владел элементами в нем, используйте контейнер указателя Boost: они предназначены для этой работы. В противном случае используйте контейнер shared_ptr
(и, конечно, используйте их правильно, что означает, что каждый, кому необходимо разделить владение, использует shared_ptr
).
В обоих случаях убедитесь, что деструктор IActor
виртуальный.
void *
требует, чтобы вы управляли памятью вручную, так что этого нет. Boost.Any является излишним, когда типы связаны наследованием - стандартный полиморфизм выполняет свою работу.
Нужен ли вам dynamic_cast
или нет - это ортогональный вопрос - если пользователям контейнера нужен только интерфейс IActor, и вы либо (а) делаете все функции интерфейса виртуальными, либо ( б) используйте идиому невиртуального интерфейса, тогда вам не нужно dynamic_cast. Если пользователи контейнера знают, что некоторые из объектов IActor "действительно" гражданские лица, и хотят использовать вещи, которые есть в гражданском интерфейсе, но не IActor, тогда вам потребуется приведение типов (или редизайн).