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