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

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

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

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

Помогите многому ценившему :).

7
задан AstroCB 17 February 2015 в 03:50
поделиться

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));
3
ответ дан 6 December 2019 в 09:18
поделиться

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

  • Определить базовый класс (который вы уже делаете) с виртуальными функциями, которые будут переопределены производными классами 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();
     }
}

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

10
ответ дан 6 December 2019 в 09:18
поделиться

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

4
ответ дан 6 December 2019 в 09:18
поделиться

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

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

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

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

3
ответ дан 6 December 2019 в 09:18
поделиться
Другие вопросы по тегам:

Похожие вопросы: