Comments 18
То есть, пока объектов мало, и все влезает в кэш (процессора) — все будет хорошо, и даже быстрее std::map какого-нибудь. А вот если объектов будут сотни тысяч, это все повылезает и будет смотреть на вас с немым укором.
Без иронии — вы гонитесь за скоростью, но используете линейный поиск. В таком случае, стоило бы пояснить, почему вы не используете структуру с логарифмическим временем доступа.
В любом случае замечание дельное и в ближайшее время постараюсь потестировать с большим количеством объектов и разными контейнерами. Благодарю за отзыв.
После небольшой доработки получится реализация паттерна 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 передавать и хранить список покупок и ид приложения, если у нас есть глобальный объект, который уже содержит эти данные.
В контексте оптимизации: могут быть тысячи объектов, требующих какой-либо сервис. Промахи кэша в высоконагруженом приложении могут стать критичными. Приведенное решение является компромиссом между медленными независимыми компонентами и быстрыми связанными.
А в случае, если мест использования несколько?
а в чем проблема запросить объект из нескольких мест? Сам-то пул является синглтоном
Кто тогда отвечает за то, что это место будет одно?
а оно не обязано быть одно. Запрашиваете объект из пула сколько угодно раз. Хотите эксклюзивный доступ? Забираете (запрашиваете + удаляете) объект из пула и используете сами.
Что помешает в пул добавить несколько одинаковых объектов?
Один и тот же объект в пул несколько раз не добавишь, по кр. мере в самой логичной версии реализации. Несколько объектов одного типа? — запрашиваем у пула полный список этих объектов и выбираем нужный. Так, например, реализована система плагинов в QtCreator'е.
Хотите эксклюзивный доступ? Забираете (запрашиваете + удаляете) объект из пула и используете сами.
То есть пул не отвечает за уникальность объекта и нужно самому потом следить за этим. Тут и есть проблема. Задача же стояла в том, чтобы заменить именно глобальные объекты, а не все подряд. Чтобы у программиста при использовании не было мыслей про то, что и где он должен проинициализировать или сохранить. В вашем варианте, как минимум, нужно ещё одно место, откуда будет забираться и удаляться объект из пула, а потом доступ к объекту нужно будет совершать через эту сохранённую переменную. В итоге, мы приходим всё к тем же минусам глобальных объектов, что и при использовании оных через extern переменные или синглтоны, но ещё через одну обёртку.
2. У вас не рассматривается случай взаимозависимости глобальных объектов и возникающие при этом проблемы с созданием и, особенно, удалением глобальных объектов.
2. Если есть объекты, которые зависят друг от друга, то и удалять и регистрировать и удалять их можно в соответствующем порядке. Опять же, в данной обёртке у вас есть контроль за порядком создания и удаления, поэтому избежать обращения одного глобального объекта к другому, когда последний уже удалился, проще.
В общем, ваш подход хорош для глобальных объектов, которые сами никого не используют, а все используют их. Например, для таблиц свойств, генератора случайных чисел и прочего. Mock-объекты создавать с таким подходом, действительно, удобно.
g_storage.AddGlobalObject<GameInfo>();
g_storage.AddGlobalObject<AI>();
И уже определённо сначала создастся GameInfo, а потом AI. При удалении будет обратный порядок
g_storage.RemoveGlobalObject<AI>();
g_storage.RemoveGlobalObject<GameInfo>();
При таком создании-удалении у AI время жизни меньше, чем у GameInfo, и возможно использование одного глобального объекта другим без боязни получить nullptr.
В нем легко допустить ошибку, особенно учитывая, что что для каждого набора Mock-объектов необходимо создать и поддерживать отдельную функцию, заполняющую g_storage.
g_storage.RemoveGlobalObject<AI>();
g_storage.AddGlobalObject<AISpecific>();
AISpecific будет выдавать тип AI в методе RecalcHashCode. Поэтому всё хранилище заменять не надо будет. Естественно, остаются сложности с поддержкой двух функций-регистраторов и подобной заменой объектов. Однако в случае с глобальными переменными это уже лучше, чем ничего.
Глобальные объекты и места их обитания