Я работаю над собственным игровым движком и в настоящее время занимаюсь проектированием своих менеджеров. Я читал, что для управления памятью использование Init()
и CleanUp()
функции лучше, чем использование конструкторов и деструкторов.
Я искал примеры кода на C ++, чтобы увидеть, как эти функции работают и как я могу реализовать их в своем движке. Как Init()
и CleanUp()
работа, и как я могу реализовать их в мой двигатель?
Ответы:
Это довольно просто, на самом деле:
Вместо того, чтобы иметь конструктор, который делает ваши настройки,
... пусть ваш конструктор вообще мало или вообще ничего не делает, и напишите метод с именем
.init
or.initialize
, который будет делать то, что обычно делает ваш конструктор.Так что теперь вместо того, чтобы просто так:
Вы можете пойти:
Преимущество заключается в том, что теперь вы можете более легко использовать инжекцию зависимостей / инверсию управления в своих системах.
Вместо того чтобы говорить
Вы можете построить солдата, дать ему метод экипировки, где вы под рукой ему оружие, а затем вызвать все остальные функции конструктора.
Так что теперь вместо того, чтобы создавать подклассы у врагов, у одного из которых есть пистолет, у другого - винтовка, а у другого - дробовик, и это единственное отличие, вы можете просто сказать:
То же самое касается разрушения. Если у вас есть особые потребности (удаление прослушивателей событий, удаление экземпляров из массивов / любых структур, с которыми вы работаете, и т. Д.), Вы должны вручную вызывать их, чтобы точно знать, когда и где в программе происходило.
РЕДАКТИРОВАТЬ
Как указал Криотан ниже, это отвечает на первоначальный пост «Как» , но на самом деле не справляется с «Почему».
Как вы, вероятно, можете видеть в ответе выше, разница между:
и писать
при этом просто имея большую функцию конструктора.
Существует аргумент для объектов, которые имеют 15 или 20 предварительных условий, что сделало бы конструктор очень, очень трудным для работы, и это сделало бы вещи проще для просмотра и запоминания, вытаскивая эти вещи в интерфейс , так что вы можете увидеть, как работает экземпляр, на один уровень выше.
Необязательная конфигурация объектов является естественным продолжением этого; необязательно установка значений в интерфейсе, прежде чем запускать объект.
У JS есть несколько отличных ярлыков для этой идеи, которые кажутся неуместными в более строгих типах c-подобных языков.
Тем не менее, есть вероятность, что, если вы имеете дело со списком аргументов, которые длинны в вашем конструкторе, ваш объект слишком велик и делает слишком много, как есть. Опять же, это вещь личного предпочтения, и есть исключения повсюду, но если вы передаете 20 вещей в объект, велика вероятность, что вы сможете найти способ заставить этот объект делать меньше, делая меньшие объекты ,
Более уместная и широко применимая причина заключается в том, что инициализация объекта основана на асинхронных данных, которых у вас нет в настоящее время.
Вы знаете, что вам нужен объект, поэтому вы все равно создадите его, но для правильной работы ему нужны данные с сервера или из другого файла, который ему сейчас нужно загрузить.
Опять же, независимо от того, передаете ли вы необходимые данные в гигантскую инициализацию или строите интерфейс, это не очень важно для концепции, поскольку это важно для интерфейса вашего объекта и дизайна вашей системы ...
Но с точки зрения построения объекта вы можете сделать что-то вроде этого:
async_loader
может быть передано имя файла или имя ресурса или что-то еще, загрузить этот ресурс - может быть, он загружает звуковые файлы или данные изображения, или, возможно, он загружает сохраненную статистику символов ...... а затем он будет передавать эти данные обратно
obj_w_async_dependencies.init(result);
.Такая динамика часто встречается в веб-приложениях.
Не обязательно в конструкции объекта для приложений более высокого уровня: например, галереи могут загружаться и инициализироваться сразу, а затем отображать фотографии по мере их потоковой передачи - это не совсем асинхронная инициализация, но там, где это наблюдается чаще, в библиотеках JavaScript.
Один модуль может зависеть от другого, и поэтому инициализация этого модуля может быть отложена до завершения загрузки зависимых элементов.
С точки зрения конкретных случаев игры, рассмотрим фактический
Game
класс.Почему мы не можем позвонить
.start
или.run
в конструктор?Ресурсы должны быть загружены - все остальное в значительной степени определено и это хорошо, но если мы попробуем запустить игру без подключения к базе данных, без текстур, моделей, звуков или уровней, это не произойдет. особенно интересная игра ...
... так в чем же разница между тем, что мы видим типичным
Game
, за исключением того, что мы даем его методу "идти вперед" имя, которое более интересно, чем.init
(или, наоборот, нарушить инициализацию еще дальше, разделить загрузку, настройка вещей, которые были загружены, и запуск программы, когда все было установлено).источник
.init
, может быть, нет, но, скорее всего. Ergo, действительный случай.Что бы вы ни читали, что лучше сказать Init и CleanUp, вам также следовало сказать, почему. Статьи, которые не оправдывают их требования, не стоит читать.
Наличие отдельных функций инициализации и выключения может упростить настройку и уничтожение систем, поскольку вы можете выбирать, в каком порядке вызывать их, в то время как конструкторы вызываются точно при создании объекта, а деструкторы - при уничтожении объекта. Когда у вас есть сложные зависимости между двумя объектами, вам часто нужно, чтобы они оба существовали до того, как они настроились, но часто это признак плохого дизайна в другом месте.
В некоторых языках нет деструкторов, на которые вы можете положиться, так как подсчет ссылок и сборка мусора затрудняют узнать, когда объект будет уничтожен. В этих языках вам почти всегда нужен метод shutdown / cleanup, а некоторым нравится добавлять метод init для симметрии.
источник
Я думаю, что лучшая причина: разрешить объединение.
если у вас есть Init и CleanUp, вы можете, когда объект убит, просто вызвать CleanUp и поместить объект в стек объекта того же типа: «пул».
Затем, когда вам понадобится новый объект, вы можете вытолкнуть один объект из пула ИЛИ, если пул пуст - слишком плохо - вам нужно создать новый. Затем вы вызываете Init для этого объекта.
Хорошей стратегией является предварительное заполнение пула до того, как игра начнется с «хорошим» количеством объектов, поэтому вам никогда не придется создавать какие-либо объекты в пуле во время игры.
Если, с другой стороны, вы используете 'new' и просто прекращаете ссылаться на объект, когда он вам бесполезен, вы создаете мусор, который необходимо вспомнить через некоторое время. Это воспоминание особенно плохо для однопоточных языков, таких как Javascript, где сборщик мусора останавливает весь код, когда он оценивает, что ему нужно вспомнить память объектов, которые больше не используются. Игра зависает в течение нескольких миллисекунд, а игровой опыт испорчен.
- Вы уже поняли: если вы объединяете все свои объекты, никаких воспоминаний не происходит, следовательно, больше нет случайного замедления.
Также гораздо быстрее вызывать init для объекта, поступающего из пула, чем выделять память + init для нового объекта.
Но повышение скорости имеет меньшее значение, поскольку создание объектов часто не является узким местом производительности ... За некоторыми исключениями, такими как безумные игры, движки частиц или физический движок, использующие для своих вычислений интенсивно двумерные / трехмерные векторы. Здесь и скорость, и создание мусора значительно улучшаются при использовании пула.
Rq: вам может не понадобиться метод CleanUp для ваших объединенных объектов, если Init () сбрасывает все.
Редактировать: ответ на этот пост побудил меня завершить небольшую статью о создании пула в Javascript .
Вы можете найти его здесь, если вам интересно:
http://gamealchemist.wordpress.com/
источник
Ваш вопрос перевернут ... Исторически говоря, более уместный вопрос:
Почему это строительство + intialisation сплавлены , то есть , почему не мы делаем эти шаги отдельно? Конечно, это идет против SoC ?
Для C ++ цель RAII заключается в том, чтобы получение и освобождение ресурса было напрямую связано с временем жизни объекта, в надежде, что это обеспечит освобождение ресурса. Является ли? Частично. Это выполняется на 100% в контексте основанных на стеке / автоматических переменных, где выход из связанной области автоматически вызывает деструкторы / освобождает эти переменные (отсюда и квалификатор
automatic
). Однако для переменных кучи этот очень полезный шаблон, к сожалению, не работает, так как вы все еще вынуждены явно вызывать ), в то же время связывая конструкцию с инициализацией, что отрицательно сказывается на следующем:delete
, чтобы запустить деструктор, и если вы забудете это сделать, вы все равно будете укушены тем, что RAII пытается решить; в контексте переменных, размещенных в куче, C ++ обеспечивает ограниченное преимущество по сравнению с C (delete
противfree()
Настоятельно рекомендуется создать объектную систему для игр / симуляций в C, поскольку она позволит пролить свет на ограничения RAII и других подобных OO-ориентированных шаблонов благодаря более глубокому пониманию допущений, которые делают C ++ и более поздние классические OO-языки. (помните, что C ++ начинался как система OO, встроенная в C).
источник