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

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

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

10 голосов
/ 22 марта 2010

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

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

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

4 голосов
/ 22 марта 2010

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

3 голосов
/ 22 марта 2010

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

Причина этого в том, что контейнер 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 голосов
/ 22 марта 2010

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

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

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

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

...