2015-02-07 174 views
2

我最近着迷于图形编程,并且我注意到许多图形引擎(即Ogre)和许多编码器总体上更喜欢动态地初始化类实例。下面是从Ogre Basic Tutorial 1希望堆栈堆栈?

//... 

Ogre::Entity* ogreHead = mSceneMgr->createEntity("Head", "ogrehead.mesh"); 
Ogre::SceneNode* headNode = mSceneMgr->getRootSceneNode()->createChildSceneNode("HeadNode"); 

//... 

ogreHeadheadNode数据成员和方法然后被称为ogreHead->blabla一个例子。

为什么要乱用对象指针而不是普通对象?

顺便说一句,我也读过的地方,堆内存分配比堆内存分配慢得多。

+0

请注意,许多库通常都有一个预先分配的对象池(因此称为“对象池”),因此它们的“新”调用比提供的“新”更快。 – CoryKramer 2015-02-07 17:40:26

+1

我不明白。 “预分配对象池”是什么意思? – Pilpel 2015-02-07 17:42:10

+4

一个对象池基本上是“我要去'新'50个'Foo'的对象,当有人叫'new'时,我会重新初始化其中的一个,让它们使用它。 '删除'我只是把对象拿回来重用。“这节省了构造函数和析构函数的开销时间。 https://en.wikipedia.org/wiki/Object_pool_pattern – CoryKramer 2015-02-07 17:43:27

回答

5

堆分配不可避免地比堆栈分配慢得多。更多关于“慢多少?”后来。但是,在许多情况下,选择是“为您制造”,原因如下:

  1. 堆栈有限。如果你用完了,应用程序几乎总是被终止 - 没有真正的好恢复,甚至打印出错信息说“我用完了堆栈”可能很难...
  2. 堆栈分配“消失”时你离开分配的功能。
  3. 变异性定义得更好,易于处理。 C++不能很好地处理“可变长度数组”,并且不能保证在所有编译器中都能正常工作。

堆栈比堆栈慢多少?

我们会稍微讨论一下“是否重要”。

对于给定的分配,堆栈分配是一个简单的减法运算[1],在此,最起码newmalloc将是一个函数调用,甚至可能是最简单的分配将是几十个指令,在复杂的情况下成千上万[因为内存必须从操作系统中获得,并清除以前的内容]。因此,从10倍到“无限”慢,给予或带走。确切的数字将取决于代码运行的确切系统,分配的大小,并且通常是“以前对分配器的调用”(例如,一个很长的“释放”分配列表可以使分配一个新对象变得更慢,因为必须搜索一个合适的适配器)。当然,除非您使用堆管理的“鸵鸟”方法,否则您还需要释放该对象并应付“内存不足”,从而为代码的执行和复杂性增加更多代码/时间。但是,通过一些合理的巧妙编程,这可能大部分是隐藏的 - 例如,在对象的整个生命周期内分配长时间分配的东西将是“无需担心”。在3D游戏中为每个像素或每个trianle分配堆中的对象将明显是一个坏主意。但是如果对象的生命周期是多帧甚至整个游戏,那么分配和释放它的时间将几乎没有。

同样,不是做10000个单独的对象分配,而是10000个对象。对象池就是这样一个概念。

此外,分配时间通常不是花费时间的地方。例如,从磁盘文件中读取三角形列表比为相同三角形列表分配空间花费的时间要长得多 - 即使分配每个三角形列表也是如此!

对我来说,规则是:

  1. 是否很好地贴合在堆栈上?通常几千字节是好的,许多千字节不太好,兆字节绝对不行。
  2. 数字(例如对象数组)是否已知,并且可以将它放在堆栈上的最大数量?
  3. 你知道对象是什么吗?换句话说抽象/多态类可能需要在堆上分配。
  4. 它的寿命与它所处的范围相同吗?如果没有,请使用堆(或进一步向下堆栈,并将其传递到堆栈)。

[1]或者如果堆栈是“向高地址增长”添加 - 我不知道有这样一个架构的机器,但它是可以想象的,我想一些已经被制定。 C当然不会承认堆栈增长的方式,或者关于运行时堆栈如何工作的任何其他内容。

+0

还有一个C++问题。我需要设计要在堆上分配的类吗?我的意思是我应该以不同的方式编写我的类,如果我知道我要在堆上创建它们的实例? – Pilpel 2015-02-07 19:39:16

+1

我通常不会。我希望我的类可以创建为“堆栈式还是堆式”[一般来说,当然有些情况下,您“知道”它们将以某种特殊方式创建,但创建方法不会改变对象的设计方式 - 这是关于“我试图解决什么问题,以及我们如何解决这个问题”] – 2015-02-07 19:41:48

+0

我刚想到一个问题。为什么有一个池,而不是让程序员确定他想要拥有多少对象,而不是在类中封装一些预分配的实例?他是否足够聪明地知道他可以在他的代码开始时为他想要或可能想要的所有对象分配数据? – Pilpel 2015-02-07 21:09:31

4

堆栈的范围是有限的:它只存在于一个函数中。现在,现代的用户接口程序通常是事件驱动的,这意味着你的函数被调用来处理一个事件,然后该函数必须返回以便程序继续运行。因此,如果您的事件处理函数希望创建一个在函数返回后仍然存在的对象,显然,该对象不能分配到该函数的堆栈中,因为函数返回后该对象将立即停止存在。这是我们为什么在堆上分配东西的主要原因。

还有其他原因。

有时候,编译期间不知道类的确切大小。如果类的确切大小未知,则不能在堆栈上创建,因为编译器需要准确了解需要为堆栈中的每个项目分配多少空间。

此外,经常使用工厂方法,如whatever::createEntity()。如果你必须调用一个单独的方法为你创建一个对象,那么这个对象不能在堆栈上创建,因为这个答案的第一段中解释了这个原因。

1

为什么用指针代替对象?

因为指针可以让事情变得更快。如果您按值传递的对象,另一个函数,例如

shoot(Orge::Entity ogre) 

,而不是

shoot(Orge::Entity* ogrePtr) 

如果怪物是不是指针,发生的事情是要传递整个对象到函数,而不是参考。如果编译器没有进行优化,则会留下一个效率不高的程序。还有其他一些原因,使用指针,你可以修改传入的对象(有些人认为引用更好,但这是一个不同的讨论)。否则,你会花费太多时间来回复制修改的对象。

为什么堆?

  1. 从某种意义上说,堆是一种更安全的内存类型,可以让您安全地重置/恢复。如果您拨打新电话并且没有记忆,您可以将其标记为错误。如果你正在使用堆栈,实际上没有什么好的方法可以知道你已经引起了堆栈溢出,没有其他监督程序,此时你已经处于危险区域。
  2. 取决于您的申请。堆栈具有本地范围,所以如果对象超出范围,它将为对象释放内存。如果你需要其他功能的对象,那么没有真正的方法来做到这一点。
  3. 对OS应用更多,堆比堆栈大得多,尤其是在多线程应用程序中,每个线程可以具有有限的堆栈大小。
+0

我想你可能会在这个答案中迂回一点。例如,如果你正在传递一个指针,那么你定义为“通过引用传递”,这在你的回答中似乎区分为不同的东西。另外,关于堆与堆栈的安全性的评论值得怀疑。不经意间覆盖堆上的内存指针也同样容易。事实上,在某些方面,堆栈更安全。虽然我们在代码中允许的溢出条件是非常严重的,但至少在堆栈展开时(假设我们没有覆盖框架),我们可以返回到良好的状态。 – 2015-02-07 19:34:55

+0

关于堆栈的公平点可以更安全,但我的观点并不是堆栈或堆被别的东西腐败。堆更容易让你知道是否有足够的内存来分配(通过检查新的返回值),而堆栈中的内存不一样。 有关引用的注意事项是,在C++中引用不能为空,而必须指向一个对象,但指针可以为null。 – embeddedninja 2015-02-12 17:05:20