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

StackOverflow https://stackoverflow.com/questions/2489533

Вопрос

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

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

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

Помощь очень ценится :).

Это было полезно?

Решение

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

Причиной этого является то, что контейнер 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, и вы либо (а) делаете все функции интерфейса виртуальными, либо (б) используете идиому невиртуального интерфейса, тогда вы не мне не нужен динамический_cast.Если пользователи контейнера знают, что некоторые из объектов IActor «на самом деле» являются гражданскими лицами, и хотят использовать вещи, которые есть в интерфейсе Civilian, но не в IActor, тогда вам потребуется приведение типов (или перепроектирование).

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top