清理堆分配对象的良好做法或约定?
Posted
技术标签:
【中文标题】清理堆分配对象的良好做法或约定?【英文标题】:Good practice or convention for cleanup heap allocated object? 【发布时间】:2013-02-14 01:57:18 【问题描述】:我正在学习 C++。我有 C、C#、ObjC 背景。相当高级的语言。
在 C# 或 ObjC 上,作为函数或方法的结果返回堆分配的对象是微不足道的。因为对象的清理是管理的(按照惯例)。它会在适当的时候被销毁。
但我不知道我应该如何在 C++ 中处理这个问题。
例如,
std::string* makeString()
std::string* str = GetSomeStringFromArbitrarySource ();
SaveSomewhereElseInternally (str);
return str;
void useString ()
std::string* str = makeString ();
// Where and how should I cleanup the `str` object?
// It is not safe `delete str` here because it may be used on another place.
当堆分配对象传递给许多函数时,清理堆分配对象的推荐和常规方法是什么?
我查看了几个智能指针,但它们看起来并没有真正降低复杂性或需要关心的事情。我误解了智能指针吗?
【问题讨论】:
这个链接 (***.com/questions/147130/…) 有一些关于 C++ 垃圾回收的非常好的信息,以及新标准 (C++11) 中关于它的具体信息。我知道这不是一个好的答案,但是,嘿,这是一个问题。 :) 为什么不只是std::string makeString()
和std::string str = makeString();
?为什么要使用堆对象?
@DavidSchwartz 因为他来自 C#,并且像 C# 一样编写代码
@Slava 公平地说,使用指针真的不像 C# ;)
@ReedCopsey 据我所知(我不是 C# 专家)那里的所有对象都是由 new 创建的。所以他可能只是在 C++ 中找到了保存 new 结果的位置并使用了它。
【参考方案1】:
我查看了几个智能指针,但它们看起来并没有真正降低复杂性或需要关心的事情。我误解了智能指针吗?
很可能,是的。在 C++ 中,由于您必须自己处理(没有 GC 会为您清理),因此您需要一种方法来跟踪每个对象的使用情况。
您可以手动将每个new
与delete
匹配,但这有时很困难或不可能,例如在上面的场景中。无法知道该对象是否正在其他地方使用。
智能指针通过为您管理生命周期来解决此问题,因此您无需删除。他们使用各种机制来跟踪对象被使用的位置,并在最后一个完成时调用delete
。
话虽如此,在这种特定情况下根本没有太多理由使用指针。如果您使用std::string
,您可以按值传递字符串,这绝不会成为问题。
【讨论】:
【参考方案2】:您误解智能指针的原因很可能与您编写的原因相同:
std::string* makeString()
而不是大多数 C++ 程序员会写的:
std::string makeString()
您需要更好地理解object lifetime in C++,然后智能指针的概念会容易得多。
【讨论】:
【参考方案3】:您需要确定对象所需的生命周期,然后使用类型系统来强制执行该生命周期。
从您的示例来看,您希望何时销毁对象并不清楚。
在 C++ 中,对象通过值传递是很常见的(因为原始类型在 Java/C#/etc 中),所以除非您需要在不同的代码段之间共享 std::string
,否则通常事情就是按值返回字符串(将makeString
写为std::string makeString()
)。
如果确实需要多个位置引用同一个对象,则应仔细考虑设计,并决定程序的哪个部分可以安全地控制对象的生命周期。在那个地方按值创建对象,然后在其他地方传递指针和引用。
【讨论】:
【参考方案4】:您可能误解了智能指针。在 C++ 中,另一种选择是程序员需要跟踪动态分配对象的生命周期和使用情况。这会影响软件的设计,也会导致人为错误的蔓延。当您使用智能指针时,对象的生命周期几乎会为您处理好。
我是在 C++ 的经典风格(没有智能指针)中长大的,所以如果需要,我可以这样编程,但如果你是从 C++ 开始,那么智能指针确实是必须的。
【讨论】:
【参考方案5】:如果您可以使用 C++11,请使用 shared pointers。这些指针实现了一种机制,一旦它们中的最后一个被销毁,就删除分配的对象。如果您使用的是 C++03,请使用 boost 的 shared pointers。如果您不能使用任何一个,请尝试将堆分配包装在您在堆栈上分配的类中,然后将引用传递给周围的那些,同时读取RAII wiki。
【讨论】:
共享指针通常是不必要的,而且它们往往会增加额外的复杂性,因为对象最终会在多个不同的代码段之间共享,因此对这些对象的更改可能会产生意想不到的非本地效果。如果可能的话,最好有一个明确的所有者。 @Mankarse,我同意任何时候有一个对象可以被多个其他对象访问,都存在非本地副作用的隐含可能性。但是,这与在一个地方创建对象有什么不同,正如您在回答中所说的那样,“然后在其他地方传递指针和引用”? 在 C++11 中,std::unique_ptr
更可能是他想要的。
@David D:显然共享指针有其用途,但很少见。我想我对这个答案的问题是思考过程——“使用共享指针,你所有的问题都会得到解决”。无论您有一个所有者还是多个所有者,您仍然需要确定对象的生命周期,并且您仍然需要准确决定哪些对象应该在哪里共享,但是一旦您做出这些决定,基于shared_ptr
的设计不传达决策以及基于单一所有者的设计。
@Nemo,当 Eonil 在他的问题中说“当它被传递给许多函数时”时,我的想法立即想到他将它们存储在多个对象中(以供长期使用),而不是仅仅传递它们随用。从这个角度来看,我完全同意 unique_ptr 更贴切。以上是关于清理堆分配对象的良好做法或约定?的主要内容,如果未能解决你的问题,请参考以下文章