Почему я должен использовать отдельные методы инициализации и очистки вместо того, чтобы вводить логику в конструктор и деструктор для компонентов двигателя?

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

Я искал примеры кода на C ++, чтобы увидеть, как работают эти функции и как я могу реализовать их в своем движке. Как работают Init() и CleanUp() и как я могу реализовать их в своем движке?

9 голосов | спросил Friso 31 Jpm1000000pmThu, 31 Jan 2013 17:49:28 +040013 2013, 17:49:28

4 ответа


11

Это довольно просто, на самом деле:

Вместо конструктора, который выполняет вашу настройку,

// c-family pseudo-code
public class Thing {
    public Thing (a, b, c, d) { this.x = a; this.y = b; /* ... */ }
}

... у вашего конструктора мало или вообще ничего нет, и напишите метод под названием .init или .initialize, который будет делать то, что обычно делает ваш конструктор.

public class Thing {
    public Thing () {}
    public void initialize (a, b, c, d) {
        this.x = a; /*...*/
    }
}

Теперь вместо того, чтобы просто выглядеть:

Thing thing = new Thing(1, 2, 3, 4);

Вы можете пойти:

Thing thing = new Thing();

thing.doSomething();
thing.bind_events(evt_1, evt_2);
thing.initialize(1, 2, 3, 4);

Преимущество заключается в том, что теперь вы можете более легко использовать зависимость-инжекцию /инверсию управления в своих системах.

Вместо того, чтобы говорить

public class Soldier {
    private Weapon weapon;

    public Soldier (name, x, y) {
        this.weapon = new Weapon();
    }
}

Вы можете построить солдата, дать ему метод экипировки, где вы вручите оружие, а THEN вызовите все остальные функции конструктора.

Итак, теперь вместо подкласса врагов, где у одного солдата есть пистолет, а у другого есть винтовка, а у другого есть дробовик, и это единственное различие, вы можете просто сказать:

Soldier soldier1 = new Soldier(),
        soldier2 = new Soldier(),
        soldier3 = new Soldier();

soldier1.equip(new Pistol());
soldier2.equip(new Rifle());
soldier3.equip(new Shotgun());

soldier1.initialize("Bob",  32,  48);
soldier2.initialize("Doug", 57, 200);
soldier3.initialize("Mike", 92,  30);

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

ИЗМЕНИТЬ


Как указал Крайотан, ниже, это отвечает «Как» исходного сообщения, но на самом деле не очень хорошо работает «Почему».

Как вы, наверное, видите в ответе выше, не может быть большой разницы между:

var myObj = new Object();
myObj.setPrecondition(1);
myObj.setOtherPrecondition(2);
myObj.init();

и записи

var myObj = new Object(1,2);

, только имея большую конструкторскую функцию.
Существует аргумент для объектов, которые имеют 15 или 20 предварительных условий, что сделало бы конструктор очень и очень трудным для работы, и это облегчило бы видеть и запоминать, вытаскивая эти вещи в интерфейс , так что вы можете увидеть, как работает экземпляр, на один уровень выше.

Дополнительная конфигурация объектов является естественным дополнением к этому; опционально устанавливая значения на интерфейсе, прежде чем сделать объект запущенным.
У JS есть несколько замечательных ссылок для этой идеи, которые просто кажутся неуместными в более сильных языках c-like.

Тем не менее, скорее всего, если вы имеете дело с длинным аргументом в своем конструкторе, ваш объект слишком велик и делает слишком много, как есть. Опять же, это личное предпочтение, и есть исключения далеко и широко, но если вы передаете 20 предметов в объект, есть вероятность, что вы могли бы найти способ сделать этот объект меньше, создав меньшие объекты .

Более подходящая причина, и широко применимая, будет заключаться в том, что инициализация объекта основана на асинхронных данных, которых у вас нет, в настоящее время.

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

Опять же, передаете ли вы необходимые данные в гигантский init или создаете интерфейс, это не очень важно для концепции, так как это важно для интерфейса вашего объекта и дизайна вашей системы ...

Но с точки зрения построения объекта вы можете сделать что-то вроде этого:

var obj_w_async_dependencies = new Object();
async_loader.load(obj_w_async_dependencies.async_data, obj_w_async_dependencies);

async_loader может получить имя файла или имя ресурса или что-то еще, загрузить этот ресурс - возможно, он загружает звуковые файлы или данные изображения, или, может быть, загружает сохраненную статистику символа ...

... и затем он будет возвращать эти данные в obj_w_async_dependencies.init(result);.

Такая динамика часто встречается в веб-приложениях.
Не обязательно в конструкции объекта для приложений более высокого уровня: например, галереи могут загружаться и инициализироватьсясразу же, а затем отображать фотографии по мере их поступления - это не инициализация async, но чаще всего это будет в библиотеках JavaScript.

Один модуль может зависеть от другого, и поэтому инициализация этого модуля может быть отложена до завершения загрузки иждивенцев.

В терминах конкретных для игры примеров рассмотрим фактический класс Game.

Почему мы не можем называть .start или .run в конструкторе?
Ресурсы должны быть загружены - все остальное в значительной степени определено и хорошо, но если мы попытаемся запустить игру без подключения к базе данных или без текстур или моделей или звуков или уровней, это не будет особенно интересная игра ...

... так что же тогда разница между тем, что мы видим в типичном Game, за исключением того, что мы даем его «идти» вперед "метод, который более интересен, чем .init (или, наоборот, разбить инициализацию еще дальше, разделить загрузку, настроить вещи, которые были загружены, и запуск программы, когда все было настроено).

ответил Norguard 31 Jpm1000000pmThu, 31 Jan 2013 21:46:56 +040013 2013, 21:46:56
13

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

Имея отдельные функции инициализации и выключения, можно упростить настройку и уничтожение систем, поскольку вы можете выбрать, в каком порядке их вызвать, тогда как конструкторы вызываются именно тогда, когда объект создается, и деструкторы вызываются при уничтожении объекта. Когда у вас есть сложные зависимости между двумя объектами, вам часто нужно, чтобы они существовали до их установки, но часто это признак плохого дизайна в других местах.

На некоторых языках нет деструкторов, на которые можно положиться, поскольку подсчет ссылок и сборка мусора затрудняют узнавание, когда объект будет уничтожен. На этих языках вам почти всегда нужен метод shutdown /cleanup, а некоторые хотели бы добавить метод init для симметрии.

ответил Kylotan 31 Jpm1000000pmThu, 31 Jan 2013 17:57:03 +040013 2013, 17:57:03
2

Я думаю, что лучшая причина: разрешить объединение.
если у вас есть Init и CleanUp, вы можете, когда объект убит, просто вызвать CleanUp и нажать объект в стек объекта того же типа: «пул».
Затем, когда вам нужен новый объект, вы можете вытащить один объект из пула или, если пул пуст - плохой, вам нужно создать новый. Затем вы вызываете Init на этом объекте.
Хорошая стратегия состоит в том, чтобы предварительно заполнить пул, прежде чем игра начнется с «хорошего» количества объектов, поэтому вам никогда не придется создавать какой-либо объединенный объект во время игры.
Если, с другой стороны, вы используете «новый» и просто перестаете ссылаться на объект, когда он вам бесполезен, вы создаете мусор, который нужно вспомнить в какое-то время. Это воспоминание особенно плохо для однопоточных языков, таких как Javascript, где сборщик мусора останавливает весь код, когда он оценивает его, он должен вспоминать память об объектах, которые больше не используются. Игра висит в течение нескольких миллисекунд, а игровой опыт испорчен.
- Вы все поняли - если вы объединяете все свои объекты, никакого воспоминания не происходит, следовательно, нет более случайного замедления.

Также гораздо быстрее вызывать init для объекта, исходящего из пула, чем выделять память + init новый объект.
Но повышение скорости имеет меньшее значение, поскольку нередко создание объектов не является узким местом производительности ... За некоторыми исключениями, такими как безумные игры, двигатели с частицами или физический движок, интенсивно использующие 2D /3d векторы для своих вычислений. Здесь и скорость, и создание мусора значительно улучшаются за счет использования пула.

Rq: вам может не понадобиться метод CleanUp для ваших объединенных объектов, если Init () сбрасывает все.

Изменить: ответ на этот пост побудил меня завершить небольшую статью, которую я сделал о объединении в Javascript .
Вы можете найти его здесь, если вам интересно:
http://gamealchemist.wordpress.com/

ответил GameAlchemist 1 FebruaryEurope/MoscowbFri, 01 Feb 2013 03:33:53 +0400000000amFri, 01 Feb 2013 03:33:53 +040013 2013, 03:33:53
0

Ваш вопрос обратный ... Исторически говоря, более подходящий вопрос:

Почему строительство + слияние слияния , т.е. почему не мы делаем эти шаги отдельно? Несомненно, это противоречит SoC ?

Для C ++ намерение RAII заключается в том, что сбор и выпуск ресурсов привязываются непосредственно к объекту в надежде, что это обеспечит выпуск ресурсов. Имеет ли это? Частично. Это 100% выполняется в контексте стековых /автоматических переменных, в результате чего связанная область автоматически вызывает деструкторы /освобождает эти переменные (следовательно, классификатор automatic). Однако для переменных кучи этот очень полезный шаблон печально ломается, так как вы все еще вынуждены явно вызывать delete, чтобы запустить деструктор, и если вы забудете сделать это, вы все равно будете укушены тем, что RAII пытается решить; в контексте переменных, распределенных по кучам, тогда C ++ обеспечивает ограниченное преимущество над C (delete vs free()), при этом конструируя конструкцию с инициализацией, которая отрицательно влияет на следующее:

Построение объектной системы для игр /симуляций в C настоятельно рекомендуется в том, что она прольет большой свет на ограничения RAII и других подобных OO-ориентированных шаблонов путем более глубокого понимания предположений о том, что C ++ и более поздние классические Языки OO (помните, что C ++ начинался как система OO, встроенная в C).

ответил Arcane Engineer 13 SatEurope/Moscow2014-12-13T16:27:16+03:00Europe/Moscow12bEurope/MoscowSat, 13 Dec 2014 16:27:16 +0300 2014, 16:27:16

Похожие вопросы

Популярные теги

security × 330linux × 316macos × 2827 × 268performance × 244command-line × 241sql-server × 235joomla-3.x × 222java × 189c++ × 186windows × 180cisco × 168bash × 158c# × 142gmail × 139arduino-uno × 139javascript × 134ssh × 133seo × 132mysql × 132