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

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

Чтобы проиллюстрировать проблему, скажем, я создаю игру, которая будет содержать разных актеров. Давайте назовем интерфейс 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, то вам потребуются приведения (или редизайн).

Другие вопросы по тегам