我正在将C#项目转换为C ++,并且在使用后有一个关于删除对象的问题。在C#中,GC当然负责删除对象,但在C ++中,必须使用delete
关键字明确地完成。
我的问题是,是否可以在整个方法中跟踪每个对象的使用情况,然后在超出范围时立即将其删除(即方法结束/重新分配)?
我知道GC在删除之前会等待一定大小的垃圾(~1MB);它是否这样做是因为使用删除时会产生开销?
由于这是我正在创建的游戏,因此可能每秒都会创建和删除大量对象,因此最好跟踪超出范围的指针,并且一旦该大小达到1MB则删除指针?
(作为旁注:稍后当游戏优化时,对象将在启动时加载一次,因此在游戏过程中没有太多要删除的内容)
答案 0 :(得分:9)
您的问题是您在C ++中使用指针。
这是你必须解决的根本问题,然后你的所有问题都会消失。有机会的话,我对这个大趋势感到厌倦,我在这个问题上创造了a set of presentation slides。 - (CC BY,请随意使用它们。)
看看幻灯片。虽然它们肯定不是很严肃,但基本信息仍然是正确的:不要使用指针。但更确切地说,消息应该是:不要使用delete
< /强>
在您的特定情况下,您可能会发现自己拥有许多长寿命的小物件。这确实是一种现代GC处理得很好的情况,以及哪些引用计数智能指针(shared_ptr
)处理效率较低。如果(并且仅当!)这成为性能问题,请考虑切换到small object allocator库。
答案 1 :(得分:5)
您应该尽可能在C ++中使用 RAII ,这样您就不必随时明确delete
任何内容。
一旦您通过智能指针和您自己的资源管理类使用RAII,您所做的每个动态分配将只存在,直到有任何可能的引用,您不必显式管理任何资源。
答案 2 :(得分:3)
C#和C ++中的内存管理完全不同。你不应该试图模仿.NET的GC在C ++中的行为。在.NET中,分配内存非常快(基本上是移动指针),而释放它是一项繁重的任务。在C ++中,由于几个原因,分配内存不是那么轻,主要是因为必须找到足够大的内存块。当在程序执行期间分配和释放多个不同大小的内存块时,堆可能会碎片化,包含许多空闲内存的小“漏洞”。在.NET中,这不会发生,因为GC会压缩堆。但是,在C ++中释放内存非常快。
.NET中的最佳实践不一定适用于C ++。例如,大多数情况下不建议在.NET中池化和重用对象,因为GC会将对象提升为更高代。 GC最适合短寿命对象。另一方面,在C ++中汇集对象对于避免堆碎片非常有用。此外,分配更大的内存块并使用 placement new 可以很好地适用于需要经常分配和释放的许多小对象,因为它可能在游戏中发生。阅读C ++中的一般内存管理技术,例如RAII或placement new。
另外,我建议你阅读“Effective C ++”和“更有效的C ++”这些书。
答案 3 :(得分:3)
嗯,最简单的解决方案可能只是使用垃圾收集 C ++。例如,Boehm收藏家运作良好。还有,有 优点和缺点(但最初用C#编写的移植代码将是一个 很可能是一个案例,其中的职业选手大大超过了利弊。)
否则,如果您将代码转换为惯用的C ++,则不应该
许多动态分配的对象需要担心。与C#,C ++不同
默认情况下具有值语义,以及大多数短期对象
应该只是局部变量,如果返回它们可能会被复制,
但没有动态分配。在C ++中,动态分配通常是
仅用于实体对象,其生命周期取决于外部事件;
例如a Monster
是在某个随机时间创建的,具有概率
取决于游戏状态,并在以后删除,在
对改变游戏状态的事件的反应。在这种情况下,你
当怪物不再是游戏的一部分时删除对象。在
C#,您可能有一个dispose
函数或类似的函数
这些对象,因为它们通常具有必须具体的行动
当他们不再存在时执行 - 例如注销
观察者,如果这是你正在使用的模式之一。在C ++中,这个
有些东西通常由析构函数处理,而不是
调用dispose
,调用delete对象。
答案 4 :(得分:1)
在C#中使用引用的每个实例中替换shared_ptr,可以在转换代码时获得最接近最小努力输入的近似值。
但是你特别提到通过一个方法使用对象并在最后删除 - 一个更好的方法是不要新建对象,而只是简单地在内联/在堆栈上实例化它。事实上,如果你采用这种方法,即使对于引入了新拷贝语义的返回对象,这也是处理返回对象的有效方法 - 因此几乎不需要在每个场景中都使用指针。
答案 5 :(得分:0)
解除分配对象时需要考虑的事情要多于在delete
超出范围时调用delete
。您必须确保只调用tr1::shared_ptr<>
一次,并且只在一次所有指向该对象的指针超出范围时调用它。 .NET中的垃圾收集器为您处理所有这些。
与C ++中的构造大致对应的构造是tr1::shared_ptr<>
,它保持对象的引用计数器,并在它下降到零时释放。让事情运行的第一种方法是将所有C#引用转换为C ++ {{1}}。然后,您可以进入那些性能瓶颈的地方(只有在您使用配置文件验证它是实际的瓶颈之后)并更改为更有效的内存处理。
答案 6 :(得分:0)