Pull to refresh

Comments 18

Вектор. С линейным поиском по хэшу. Но вектор. Тут что-то явно не логично.
То есть, пока объектов мало, и все влезает в кэш (процессора) — все будет хорошо, и даже быстрее std::map какого-нибудь. А вот если объектов будут сотни тысяч, это все повылезает и будет смотреть на вас с немым укором.

Без иронии — вы гонитесь за скоростью, но используете линейный поиск. В таком случае, стоило бы пояснить, почему вы не используете структуру с логарифмическим временем доступа.
Предположительно, в проекте не будет больше 30-40 глобальных переменных. В том же CryEngine насчитал в структуре около 45. С этим расчётом разрабатывалось и тестировалось. В случае, если количество глобальных объектов переваливает за сотню, то, возможно, с архитектурой проекта не всё хорошо.
В любом случае замечание дельное и в ближайшее время постараюсь потестировать с большим количеством объектов и разными контейнерами. Благодарю за отзыв.

После небольшой доработки получится реализация паттерна Dependency Injection Container (контейнер внедрения/инъекции зависимостей, IoC container).


Конкретно, отличия предложенной реализации от "классической":


  • Ключами в контейнере являются интерфейсы:


    dic.get<IInAppPurchases>()
    
    dic.get<IGameConfig>()

  • Регистрируются "фабрики" объектов, вместо экземпляров. Это позволяет конструкторам одних объектов получать доступ к методам других, не беспокоясь о порядке инициализации (пока у нас нет циклических зависимостей):


    div.registerInstance<IInAppPurchases>([](DIC& dic){ 
      return new AppStoreInAppPurchases(dic.get<IConfiguration>().getPurchasesList(),
           dic.get<IConfiguration>().get("appstore.appid")); 
    });
    
    div.registerInstance<IGameConfig>([](){ 
      return new XMLConfiguration("gameconfig.xml"); 
    });

  • В соответствии с предыдущим пунктом, экземпляры создаются лениво.


  • Также предложенная реализация предоставляет не свойственный контейнеру зависимостей функционал получения списка объектов.

Небольшое замечание по реализации: type_info::hash_code не обязана возвращать разные значения для разных типов. Вероятность столкнуться с этой проблемой невелика, пока число объектов мало, но парадокс дней рождения не дремлет. В С++11 есть std::type_index специально для целей поиска по типу в контейнерах.

Вы пытаетесь один паттерн подменить другим.
Зачем в AppStoreInAppPurchases передавать и хранить список покупок и ид приложения, если у нас есть глобальный объект, который уже содержит эти данные.
В контексте оптимизации: могут быть тысячи объектов, требующих какой-либо сервис. Промахи кэша в высоконагруженом приложении могут стать критичными. Приведенное решение является компромиссом между медленными независимыми компонентами и быстрыми связанными.
Насколько я понимаю, пул объектов предназначен для того, чтобы во время использования получить готовый объект, а не создавать его. В пуле может быть множество объектов одинакового типа. Также он рассчитывается на достаточно большое количество объектов. Расчёт при разработке был в другом — небольшое количество объектов, которые будут храниться в единственном экземпляре. Здесь есть что-то и от пула объектов, и, как уже писали, от паттерна Dependency Injection.
не совсем. В одном месте объект инициализируется и помещается в пул, в другом — запрашивается и используется. Можно запросить объект и запомнить по месту использования — тогда время доступа к объекту в пуле будет влиять только на время инициализации приложения. Можно забирать объекты и деинициализировать вручную. То есть в целом этот паттерн решает как раз таки ваши проблемы.
А в случае, если мест использования несколько? Кто тогда отвечает за то, что это место будет одно? Что помешает в пул добавить несколько одинаковых объектов? Ведь данный паттерн будет решать данные вопросы только в случае доработки и введения данных ограничений.
А в случае, если мест использования несколько?

а в чем проблема запросить объект из нескольких мест? Сам-то пул является синглтоном
Кто тогда отвечает за то, что это место будет одно?

а оно не обязано быть одно. Запрашиваете объект из пула сколько угодно раз. Хотите эксклюзивный доступ? Забираете (запрашиваете + удаляете) объект из пула и используете сами.
Что помешает в пул добавить несколько одинаковых объектов?

Один и тот же объект в пул несколько раз не добавишь, по кр. мере в самой логичной версии реализации. Несколько объектов одного типа? — запрашиваем у пула полный список этих объектов и выбираем нужный. Так, например, реализована система плагинов в QtCreator'е.
Хотите эксклюзивный доступ? Забираете (запрашиваете + удаляете) объект из пула и используете сами.

То есть пул не отвечает за уникальность объекта и нужно самому потом следить за этим. Тут и есть проблема. Задача же стояла в том, чтобы заменить именно глобальные объекты, а не все подряд. Чтобы у программиста при использовании не было мыслей про то, что и где он должен проинициализировать или сохранить. В вашем варианте, как минимум, нужно ещё одно место, откуда будет забираться и удаляться объект из пула, а потом доступ к объекту нужно будет совершать через эту сохранённую переменную. В итоге, мы приходим всё к тем же минусам глобальных объектов, что и при использовании оных через extern переменные или синглтоны, но ещё через одну обёртку.
я не пытаюсь доказать, что пул объектов лучше вашего решения. Я пытаюсь доказать, что они практически одно и то же
Соглашусь в том, что они очень похожи. Если обобщить, то обёртка это доработанный пул объектов с некоторыми ограничениями.
1. Кто отвечает за инициализацию глобальных объектов? В вашем примере, насколько, я понимаю, этим занимается функция Test().
2. У вас не рассматривается случай взаимозависимости глобальных объектов и возникающие при этом проблемы с созданием и, особенно, удалением глобальных объектов.
1. За инициализацию объектов отвечает программист. Но так как у вас есть контроль за порядком создания объектов, то это облегчает весь процесс.
2. Если есть объекты, которые зависят друг от друга, то и удалять и регистрировать и удалять их можно в соответствующем порядке. Опять же, в данной обёртке у вас есть контроль за порядком создания и удаления, поэтому избежать обращения одного глобального объекта к другому, когда последний уже удалился, проще.
Если глобальные объекты используют друг друга, значит программист ответственен за то, чтобы они создавались и удалялись в правильном порядке. Более того, правильного порядка удаления может и не существовать, так как при любом порядке уже удаленный глобальный объект может снова понадобиться, и тогда его придется пересоздавать.

В общем, ваш подход хорош для глобальных объектов, которые сами никого не используют, а все используют их. Например, для таблиц свойств, генератора случайных чисел и прочего. Mock-объекты создавать с таким подходом, действительно, удобно.
Не совсем. Допустим, у нас есть GameInfo, который содержит информацию об игровых объектах. И есть система AI, которой нужны игровые сущности. В случае глобальных объектов нету уверенности, что сначала создастся GameInfo и AI не будет при создании использовать не инициализированный указатель. В случае с обёрткой вы можете написать
	g_storage.AddGlobalObject<GameInfo>();
	g_storage.AddGlobalObject<AI>();

И уже определённо сначала создастся GameInfo, а потом AI. При удалении будет обратный порядок
	g_storage.RemoveGlobalObject<AI>();
	g_storage.RemoveGlobalObject<GameInfo>();

При таком создании-удалении у AI время жизни меньше, чем у GameInfo, и возможно использование одного глобального объекта другим без боязни получить nullptr.
Просто глобальные переменные с неопределенным порядком инициализации, разумеется, хуже любого организованного подхода. Но у организованного подхода тоже есть свои проблемы. Например, у вашего — это код, выполняющий g_storage.AddGlobalObject<> / g_storage.RemoveGlobalObject<>.
В нем легко допустить ошибку, особенно учитывая, что что для каждого набора Mock-объектов необходимо создать и поддерживать отдельную функцию, заполняющую g_storage.
Согласен, в коде всё так же можно допустить ошибку. По поводу Mock-объектов, то для них возможна замена проще, чем переписывать функцию, заполняющую g_storege. Допустим, есть набор глобальных объектов, которые необходимы для реального запуска. Этот же набор дублируется для всех тестов. Данные объекты-дубликаты будут с пустыми реализациями виртуальных методов, чтобы можно было их создать. Есть объект AI, который необходимо заменить на конкретизированный для тестов.
g_storage.RemoveGlobalObject<AI>();
g_storage.AddGlobalObject<AISpecific>();

AISpecific будет выдавать тип AI в методе RecalcHashCode. Поэтому всё хранилище заменять не надо будет. Естественно, остаются сложности с поддержкой двух функций-регистраторов и подобной заменой объектов. Однако в случае с глобальными переменными это уже лучше, чем ничего.
Sign up to leave a comment.