为什么不建议在堆上创建std::string/std::map?

7
我注意到在多个问题中,C++专家询问是否应该使用'new'关键字创建std :: string / std :: map /等(对于C++新手来说,如果不是很明显)。
所以,如果我理解正确,这将不会在堆上创建它,而是在栈上创建它。这意味着在函数超出范围时,对象将消失,但我相信这不是情况,我的理解是错误的。
这是因为底层模板在堆上实例化它并使用auto_ptr进行管理,以免造成内存泄漏吗?这是否适用于所有stl类?
另外,一个跟进问题是,创建插入地图中的对象的方法应该是什么?它们应该在堆上分配(如果它们在函数范围之外有价值)吗?
编辑:
我确实理解堆和栈之间的区别以及使用每个原因(我可能没有表达清楚)。
我之所以问这个问题是因为它似乎不自然,只是在堆栈上实例化一个我想保留的对象。但是,我想这只是语法的样子。
这意味着,当我写下以下内容时,我感觉自己正在使用栈:
std::map<int,int> mymap;

替代,

std::map<int,int> *mymap = new std::map<int,int>;

我也在思考这对内存的影响。由于现在内存是由该实现本身进行清理,这是否类似于Java中的垃圾回收?在使用STL对象时是否存在隐含的性能影响?


你放进去的东西会被复制。不过我不知道你在 auto_ptr 方面想表达什么。我怀疑任何实现都不会在任何容器的实现中使用它。在栈上创建对象的好处是,你不必管理它的内存,因为当对象超出作用域时,它就会被释放。这也更加安全,可以避免异常。 - chris
一旦函数超出作用域,对象就会消失。如果这是你的关注点,你就不应该首先在堆上创建对象。 - Appleshell
也许一个具体的代码示例,包括预期的问题或流程,会帮助我们看清楚其中一个人可能与其他人存在不同的术语。 - chris
只有在你的方法返回后需要对象持久存在时才需要使用 new。这不仅适用于字符串或映射等特定类型,而是一个普遍规则。 - Patashu
如果有机会的话,请查阅Scott Meyers的Effective C++。其中有一章讲解使用智能指针来防止内存泄漏。它特别讨论了下面某位张贴者提到的“RAII”惯用法。一个简单的例子是使用new来初始化某些内容,但在处理中途,有什么东西抛出异常导致程序控制永远无法到达宝贵的delete语句,后者本应正确地释放内存。这就是你的内存泄漏 :)使用智能指针就不必担心这个问题了。 - Nobilis
2个回答

16

这不只适用于std::stringstd::map,这是关于C++中几乎所有对象的一般规则。

为了自动化资源管理,通常需要将每个资源绑定到一个范围中,该范围包含资源所需的时间。假设您始终如一地执行此操作,则在执行离开定义它的作用域时,每个资源都会自动清理。

这通常称为“RAII”(Resource Acquisition is Initialization),尽管有些人使用更具描述性的术语SBRM(Scope bound resource management)。无论使用哪种术语,如果适当且一致地使用,它都可以非常有效地工作。

“RAII”术语的原因在于,这意味着大多数资源都是在对象构造期间获取的,并在对象销毁期间释放的。这往往会导致一种相当特定的编码风格,其中(除其他事项外)对象相当粒度细,每个对象都管理一个特定资源的生命周期。


嗯...SBRM是“范围绑定”资源管理。 - Captain Obvlious
@CaptainObvlious:哎呀,已经改正了。谢谢。 - Jerry Coffin
2
@dev_nut:这只是在离开作用域时对变量进行正常销毁。除非你清理了大量的资源,否则这个过程通常不会花费太多时间。与垃圾回收器(GC)不同的是,垃圾回收器需要花费相当一部分时间来确定哪些变量仍在使用中。 - Jerry Coffin
在我看来,好的C++代码不应该在客户端代码中有任何新内容,只应该在库中存在。 - Serve Laurijssen
2
@dev_nut:不会——如果您删除它们,映射中的项目将被销毁。如果它是局部变量(例如在某些函数中),则在其超出范围时销毁该映射本身,或者在您明确销毁它时(如果您动态分配了它)。就长时间存在而言,通常只是找到正确的函数问题。在极端情况下,它将在整个程序的生命周期内存在,因此可能在“main”开始时定义。 - Jerry Coffin
显示剩余2条评论

10

将某些东西放在堆上的常见原因之一是大小在编译时未知,并且您需要灵活地分配一些不确定数量的对象。 stringmap等容器的内部实现将根据需要自动从堆中分配,减轻了您的负担。 将对象本身放在堆上变得多余。

仅在需要容器的生存期长于创建它的块时才将其放在堆上。


1
+1。特别是对于“将容器放在堆上的唯一原因是当您需要其生命周期比创建它的块更长时。” - 又一个这样的“永远/总是”规则被推翻了。而且这是完全正确的。 - Daniel Kamil Kozar
即使对象的生命周期比创建它的块更长,使用 NRVO 时,通常不需要将容器放在堆上,因为通过值返回将避免所有开销(直接构造到调用者提供的空间中,通常是在其自己的堆栈帧上)。 - ShadowRanger

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接