我正在开发自己的游戏引擎,目前正在设计经理。我已经读过,对于内存管理,使用Init()
和CleanUp()
函数比使用构造函数和析构函数更好。
我一直在寻找C ++代码示例,以查看这些功能如何工作以及如何将其实现到引擎中。如何Init()
和CleanUp()
工作,我如何能实现他们进入我的引擎?
我正在开发自己的游戏引擎,目前正在设计经理。我已经读过,对于内存管理,使用Init()
和CleanUp()
函数比使用构造函数和析构函数更好。
我一直在寻找C ++代码示例,以查看这些功能如何工作以及如何将其实现到引擎中。如何Init()
和CleanUp()
工作,我如何能实现他们进入我的引擎?
Answers:
实际上很简单:
而不是由构造器来完成您的设置,
// 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();
}
}
你可以建立士兵,给他装备的方法,在这里你的手他的武器,然后调用所有的构造函数的休息。
因此,现在,与其将一个士兵拿着手枪,另一名士兵拿着步枪,另一名士兵拿着shot弹枪的敌人分类,这是唯一的区别,您可以说:
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);
同样处理破坏。如果您有特殊需要(删除事件侦听器,从数组中删除实例/正在使用的任何结构等),则可以手动调用它们,以便准确了解程序中发生的时间和位置。
编辑
正如Kryotan在下面指出的那样,这回答了原始帖子的“如何”,但实际上并没有很好地完成“为什么”的工作。
正如您可能在上面的答案中看到的,它们之间可能没有太大区别:
var myObj = new Object();
myObj.setPrecondition(1);
myObj.setOtherPrecondition(2);
myObj.init();
和写作
var myObj = new Object(1,2);
同时具有更大的构造函数。
对于具有15或20个前提条件的对象,有一个论点是,这会使构造函数非常非常难以使用,并且通过将这些东西拉出接口,可以使事情变得更容易看到和记住。 ,这样您就可以看到实例化的工作原理,更高一级。
对象的可选配置是对此的自然扩展。在使对象运行之前,可以选择在接口上设置值。
JS为这个想法提供了一些很棒的捷径,在更强类型的类似c的语言中似乎显得格格不入。
就是说,如果您要处理构造函数中很长的参数列表,则可能是您的对象太大而不能做太多事情。再说一遍,这是个人喜好,并且有很多例外,但是如果您将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);
。
这种动态现象经常出现在网络应用中。
对于高层应用程序,不一定是在对象的构造中:例如,画廊可能会立即加载和初始化,然后在流进照片时显示照片,这实际上不是异步初始化,但是在大多数情况下会是在JavaScript库中。
一个模块可能依赖于另一个模块,因此该模块的初始化可能会推迟到依赖项的加载完成之前。
就特定于游戏的实例而言,请考虑一个实际的Game
类。
我们为什么不能打电话.start
或.run
在构造函数?
需要加载资源-其余所有内容都已经定义好并且可以使用,但是如果我们尝试在没有数据库连接,没有纹理,模型,声音或级别的情况下运行游戏,那将是不可能的。一个特别有趣的游戏...
...那么,我们所看到的典型值之间的区别是什么Game
,除了我们给它的“ gogo”方法取一个比.init
(或相反,将初始化进一步分开,以分开加载,设置已加载的内容,并在完成所有设置后运行程序)。
.init
,也许不需要,但是可能。嗯,有效的情况。
无论您读了什么说Init和CleanUp更好,都应该告诉您原因。不证明其主张的文章不值得阅读。
拥有单独的初始化和关闭函数可以使建立和销毁系统变得更加容易,因为您可以选择调用它们的顺序,而构造函数在创建对象时被准确调用,而销毁对象时被销毁。当您在2个对象之间具有复杂的依赖关系时,通常需要在设置它们之前先将它们都存在-但这通常表明在其他地方设计不良。
某些语言没有您可以依赖的析构函数,因为引用计数和垃圾回收使您更难知道何时销毁对象。在这些语言中,几乎总是需要使用shutdown / cleanup方法,并且有些人喜欢为对称添加init方法。
我认为最好的原因是:允许合并。
如果您具有Init和CleanUp,则可以在对象被杀死时调用CleanUp,然后将该对象推入相同类型的对象堆栈中:“池”。
然后,无论何时需要新对象,都可以从池中弹出一个对象,或者如果池为空-太糟糕了,则必须创建一个新对象。然后,在此对象上调用Init。
好的策略是在游戏开始时先填充大量“好的”对象来预先填充池,因此您在游戏过程中不必创建任何池化的对象。
另一方面,如果您使用“ new”,并且在对对象无用时停止引用该对象,则会创建必须在某个时间重新收集的垃圾。对于像Javascript这样的单线程语言,这种重新收集尤其是一件坏事,垃圾收集器在评估需要重新收集不再使用的对象的内存时会停止所有代码。游戏会在几毫秒内挂起,并且会破坏游戏体验。
-您已经很好地理解了-:如果您将所有对象合并在一起,则不会发生任何回忆,因此不会再出现随机减速。
调用来自池的对象的init比分配内存+初始化新对象要快得多。
但是,提高速度的重要性不大,因为对象创建通常不是性能瓶颈...除了少数例外,例如疯狂游戏,粒子引擎或使用密集2D / 3d向量进行计算的物理引擎。在这里,使用池极大地提高了速度和垃圾创建。
Rq:如果Init()重设了所有内容,则可能不需要为合并的对象提供CleanUp方法。
编辑:回复这篇文章促使我完成了我写的有关在Javascript中合并的小文章。
如果您有兴趣,可以在这里找到:http :
//gamealchemist.wordpress.com/
您的问题已被逆转...从历史上讲,更相关的问题是:
为什么是 建设+ intialisation合二为一,即为什么不,我们做这些步骤分开?当然这与SoC背道而驰吗?
对于C ++,RAII的目的是将资源的获取和释放直接与对象生存期相关联,以希望这可以确保资源的释放。可以?部分地。它是在基于堆栈的/自动变量的上下文中100%满足的,在这种情况下,离开关联的范围会自动调用析构函数/释放这些变量(因此是qualifier ),同时将构造与初始化结合在一起,这对以下方面产生负面影响:automatic
)。但是对于堆变量,这个非常有用的模式令人遗憾地崩溃了,因为您仍然被迫显式调用delete
以便运行析构函数,并且如果您忘记这样做,您仍然会被RAII试图解决的问题所束缚。在堆分配变量的情况下,然后,C ++提供对C有限的益处(delete
VSfree()
强烈建议使用C构建游戏/模拟的对象系统,因为它将通过更深入地理解C ++和后来的经典OO语言所做出的假设,为RAII和其他此类以OO为中心的模式提供很多启示。 (请记住,C ++最初是用C内置的OO系统)。