清理堆分配对象的良好做法或约定?

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 会为您清理),因此您需要一种方法来跟踪每个对象的使用情况。

您可以手动将每个newdelete 匹配,但这有时很困难或不可能,例如在上面的场景中。无法知道该对象是否正在其他地方使用。

智能指针通过为您管理生命周期来解决此问题,因此您无需删除。他们使用各种机制来跟踪对象被使用的位置,并在最后一个完成时调用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 更贴切。

以上是关于清理堆分配对象的良好做法或约定?的主要内容,如果未能解决你的问题,请参考以下文章

java温习---对象

12-JAVA清理_终结处理和垃圾回收

域对象与其关系表的良好命名约定是啥?

静态方法内存分配

清理大型遗留 Java 项目

java基础10