2014-01-23 3 views
1

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

Я начал что-то вроде этого:

class EntityManager { 
    // ... 
    private: 
    map<string, Entity> _entities; 
} 
// definition of some add method 
void EntityManager::add (...) { 
    _entities.emplace(std::piecewise_construct, 
         std::forward_as_tuple(...), 
         /* and so on */); 
} 

где карта будет занимать весь игровой объект (который фиксируется в размере), и добавление будет включать emplace.

Вопрос 1. Является ли это умным? Или это неэффективно каким-то образом против удерживания указателя как значения на карте?

Вопрос 2. Скажите, что Entity (или какой-то подкласс) в какой-то мере стал динамическим. Есть ли что-нибудь, чтобы остановить этот подход от его обработки?

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

Entity& EntityManager::get(std::string entityId) 
{ 
    auto results = _gameEntities.find(entityId); 
    if (results == _gameEntities.end()) { 
     return Entity(); //this is bad, right? 
    } 

    return results->second; 
} 

Вопрос 3. Конечно, возвращающая ссылку на локальный стек распределённая объект плохо. Поскольку я не могу вернуть NULL, это единственное решение для использования исключений?

Единственная «проблема» с этим map<string, Entity> подходом я заметил, что я могу только внести изменения в объект игры, который я создаю первым add ИНГ его, затем запрашивает ссылку на него с get, а затем внести изменения. Это немного уродливо, а также добавляет некоторые накладные расходы на создание объектов.

Так что теперь, я переключаюсь на конструкции указателя:

class EntityManager { 
    // ... 
    private: 
    map<string, unique_ptr<Entity>> _entities; 
} 

Это дает мне два варианта, как обрабатывать добавление элементов

// OPTION 1: create a unique_ptr elsewhere, then move it. 

void EntityManager::add(string id, unique_ptr<Entity> ptr) { 
    _entities.insert(std::make_pair(id, std::move(ptr))); 
} 

// object creation 
unique_ptr<Entity> uPtr(new Entity()); 
_entityManager.add("entity1", std::move(uPtr)); 

// OR 

// OPTION 2: Create a naked pointer elsewhere, create a unique_ptr on insert 

void EntityManager::add(string id, Entity* ptr) { 
    _entities.insert(make_pair(id, unique_ptr<Entity>(ptr))); 
} 

// object creation 
Entity* e = new Entity(); 
_entityManager.add("entity1", e); 

Вопрос 4. Есть ли эффективно разницу между эти подходы? Или предпочтительный способ?

Я думаю, что это все конкретные предметы, которые у меня есть, спасибо за чтение. На самом деле ...

Вопрос 5. Есть ли что-нибудь, что выпрыгивает как неправильное/заметное, что я пропустил?

+2

** 5 вопросов ** в одном !! Это не совсем подходит для формата SO Q/A. Лучше спросить ** один ** главный вопрос, например, например._'Что я могу сделать, чтобы улучшить дизайн/реализацию класса EntityManager'_ и перечислить ваши сомнения в списке. –

+1

А затем разместите его на codereview. –

+0

Этот вопрос не соответствует теме, потому что он принадлежит [Stack Exchange Codereview] (http://codereview.stackexchange.com/). Извините, формат не подходит для SO! –

ответ

3

Вопрос 1: Я не вижу ничего явно неправильного в этом подходе. Мне кажется разумным с точки зрения эффективности.

Вопрос 2: В C++ конкретные классы и типы всегда имеют фиксированный размер, поэтому я не понимаю ваш вопрос.Когда вы говорите о типе будучи динамическим размером, я могу думать о двух толкований:

  • Класс внутренне распределяет и управляет свободно хранить память, увеличивая «фактический» размер объекта динамически. (Этот случай также относится к классу, который имеет такой «динамический» элемент данных, например std::string.) В этом случае тип структурно фиксированного размера, хотя и семантически измененный, но все это скрыто за конструкторами типа, деструктор и операторы присваивания. Следовательно, я бы сказал, что это не оказывает существенного влияния на ваш дизайн.
  • Контейнер на самом деле предназначен для хранения гетерогенных типов, связанных общим базовым типом. В этом случае вы не можете хранить объекты непосредственно в map (или другом стандартном контейнере), поскольку они предназначены для хранения только однородных типов. Вы должны сохранить указатель или указатель-тип типа (например, shared_ptr<Entity> или unique_ptr<Entity>), и вам также, вероятно, также нужно будет уделить особое внимание тому, как эти объекты создаются. Есть много деталей и компромиссов в решении этих вопросов, поэтому я не думаю, что могу дать конкретный ответ на этот вопрос без особых деталей.

emplace() будет выделять объект в кучу, но (скорее всего) только косвенно. Более конкретно, ваши объекты будут обернуты во внутренние структуры узлов, которые сами выделяются в куче. Эти структуры узлов будут содержать ваши объекты вместе с указателями на другие узлы и/или другую учетную информацию, поддерживаемую map.

Вопрос 3: Если объект, который вы ищете, не может существовать, то есть одна причины, чтобы рассмотреть возможность использования указателей, так как они обнуляемые и могут быть проверены на nullptr. Если вы должны вернуть ссылку, а не указатель, исключения являются разумным выбором (как это неявно добавляет объект, как вы упоминаете), но другой вариант заключается в использовании прототипа объекта static и возвращении ссылки на него:

Entity &EntityManager::get(string const &entityId) { 
    static Entity empty{}; 
    auto found = gameEntities_.find(entityId); 
    if (found == end(gameEntities_)) 
     return empty; 
    return found->second; 
} 
// (Note that leading underscores are reserved identifiers 
// in some contexts and some such identifiers are reserved 
// everywhere, so I prefer to avoid them altogether.) 

Однако поймите, что этот подход делает ваш объект не-потоковым, поскольку один и тот же статический объект может использоваться несколькими потоками, которые не знают друг о друге. Эта проблема уходит, если get() возвращает Entity const &, а не Entity &, поскольку переменные только для чтения по сути являются потокобезопасными. Другой вариант заключается в том, чтобы сделать empty переменным нить-локальным, но это несет определенную стоимость, а также вводит некоторые удивительные поведения, если значение empty изменено --- изменения будут видны после последующих вызовов get().

Вопрос 4: Да, между этими двумя подходами существует существенная разница. В частности, ВАРИАНТ 2 не является исключением. Вы проходите id по значению до add(), а затем передаете его по значению в make_pair(). Это две возможности для создания конструктора копии string, который пропустит ptr.

(Кроме того, небольшая точка, в ВАРИАНТ 1, вам необходимо move(uPtr) на месте вызова, так как unique_ptr<T> подвижен, но не копируемыми.)

Вопрос 5: Мои комментарии включены в мой ответы на вопросы 1-4.

+0

Спасибо за тщательный ответ. Что касается вашего ответа на вопрос Q4: в чем разница между двумя параметрами в строке? Они оба, похоже, справляются с этим одинаково. И каков правильный путь? Используйте ссылку или указатель, а затем скопируйте это значение на карту? И в отношении этого «второстепенного пункта» это был надзор; но его все равно нужно переместить дважды правильно? Один раз в вызов метода и один раз в тело метода при вставке. – esel

+1

@esel, разница между двумя параметрами заключается в том, что в _OPTION 1_ указатель 'new'-ed хранится в' unique_ptr', как только он создается, что означает, что он не будет течь даже при копировании 'id' throws --- stack unwinding уничтожит 'unique_ptr' и очистит память. В _OPTION 2_ указатель остается открытым и восприимчивым к утечке до создания пары в make_pair(). К этому моменту у вас будет одна конструкция «строка» и до двух «строковых» копий, которые могут быть выброшены, протекая с помощью необработанного указателя. –

+1

@esei, в отношении «второстепенной точки», да, у вас будет два хода. Вообще ходы дешевы и не бросаются, так что это не главное. Перемещение 'unique_ptr' может даже быть столь же дешевым, как простая копия указателя, или может в некоторых случаях полностью исключаться компилятором. Но в любом случае перемещение 'unique_ptr' никогда не должно быть дороже, чем два назначения указателя. –

Смежные вопросы