【问题标题】:Object deletion from heap space从堆空间中删除对象
【发布时间】:2013-05-12 18:07:38
【问题描述】:

在 main 函数中,我使用 new 创建一个对象并且不删除它。我希望进程退出后会清除堆空间。下面是示例代码,其中 A 类的对象是 B 类的成员变量. B类也有一个multimap作为成员变量。

    Class A
    {
    Public:
    A(); //have definition in cpp file
    ~A();//have definition in cpp file
    Private:
    Int a;
    };

    Class B{
    Private:
    Std::multimap<string,string> map_test;
    Public:
    A a;
    B(); //have definition inn cpp file
    ~B();//does not have any definition in cpp file
    };

    int main()
    {
      B *b = new B();
      /* code section where it fills some 1000 key value pairs in the multimap 
         for some purpose */
      return 0;

    }

我的理解:

  1. 即使我不在这里删除对象,它也不会产生任何问题,因为一旦进程退出,堆空间就会被清理。因为我的程序范围如上所述,没有其他人会重用它.那么不使用delete是好是坏?您对此有何建议?
  2. 它应该调用对象的默认析构函数,然后调用隐式多映射析构函数。所以它不需要明确清除多图。如果我错了,请纠正我。
  3. 在父类中只是声明了析构函数,并没有任何定义。所以会调用隐式析构函数还是忽略调用它?(没有不定义的原因,只是要求更好的理解。)李>
  4. 如果在父类的情况下调用隐式析构函数,是否应该调用这里定义的子类析构函数?
  5. 由于父类对象是使用 new 实例化的,它将在堆中创建。然后将存储此父对象的成员变量的确切位置。例如对象“a”是一个成员变量,并通过查看声明这个成员对象,似乎它会在堆栈中创建。我在这里很困惑父对象及其成员子对象的确切内存创建是如何发生的。你能帮我理解一下吗?

【问题讨论】:

  • 已编辑:将派生/基础更改为父/子

标签: c++ heap-memory


【解决方案1】:

你问了很多问题。

首先,即使进程退出并清除所有内存(就像它所做的那样),它的最佳实践也是 alwsys 清除内存。总是处理它..使用 shared_ptr 很容易......

析构函数总是以正确的顺序被调用,但是你的多重映射是危险的,因为你应该清除多重映射中的元素,就像你存储指针一样,它会导致严重的泄漏

【讨论】:

  • 是的,你是对的 :) 我刚刚发布了所有出现在我脑海中的问题。感谢您的建议。
  • 最近开始使用 stackoverflow..一旦我超过 15 个声望点,我将有资格投票 :)
【解决方案2】:
  1. 可以,只要您的对象是在 main.c 中创建的。 但是,如果您想更改它,例如创建 B 的多个实例,或者在另一个类中使用它,等等等等,那就是另一回事了。此外,像 valgrind 这样的内存检查工具会给你一个新的 w/o delete 的误报,你会很想忽略它。但是,如果它成为一种习惯,你可能会忽略真正的内存泄漏。

  2. 正确,现在如果是map&lt;string, string*&gt;,那么您可能需要清理

  3. 它将调用默认析构函数

  4. 会的

  5. 我猜,你问的是基类成员变量存储在哪里?它们也存储在堆上。它们在内存中的派生类字段之前。

【讨论】:

  • 感谢您提供宝贵的答案。我刚刚编辑了问题并将它们更新为父类和子类,而不是派生类和基类。但是你的回答是有道理的,我现在很清楚第 5 点。
【解决方案3】:
  1. 大多数操作系统会在进程退出时清理进程的堆空间。如果有一堆嵌入式操作系统并非如此,我不会感到惊讶,而且这仍然是一种不好的做法,因为您确实存在内存泄漏。
  2. 当您泄漏内存时,不会调用任何析构函数。也就是说,如果你没有实现析构函数并依赖于编译器生成的默认析构函数,你也不应该声明它——事实上我很惊讶你没有收到链接器错误。是的,只要您遵守容器对值语义的要求(即,没有原始指针),multimap 的默认析构函数就会删除 multimap 的内容。
  3. 这是一种不好的做法,作为程序员,您应该确保始终正确地管理您的资源。另外,我看不出有任何理由分配b,您可以在堆栈上创建对象,而不必考虑资源管理。
  4. 如果确实声明了它,则必须提供一个实现,因为析构函数的声明将隐式禁用编译器生成的析构函数。这就是为什么我在上面说我很惊讶您没有收到链接错误。
  5. 编译器将通过遍历层次结构并以相反的构造顺序调用析构函数来处理基类的析构。
  6. 不,包含 A 和 B 的整个对象将在堆上构造 - 这就是为什么您可以将指向 B 的指针作为指向 A 的指针的别名,并与派生类进行交互,就好像它是基类一样。李>

【讨论】:

  • 感谢您提出宝贵的意见。我对我的问题进行了更正,将派生/基础更改为父/子。这是我没有收到链接器错误的原因吗?
  • 关于第 3 点:如果该类确实有类似 multimap 的成员,则可能会占用更多空间。那么将它放在堆中会更好吗?
  • 在我所知道的每个实现中,multimap 的有效负载都将在堆上分配,因此除非有另一个好的设计,否则无需显式在堆上分配 multimap原因。
【解决方案4】:

事实:C++ 中没有本机(在表下)垃圾收集,尽管有很多东西可以强制执行某种垃圾收集。

因此,在您的代码中,您有内存泄漏。在您分配 B 的范围结束时,您分配的内存_不是 free'd

浏览你的问题列表:

  1. 不,如果你不删除指针,内存将不会被释放。关于这个主题,您可以做一些事情:

    • 使用RAII idiom(资源获取是初始化),即使用对象来管理内存:
    • 使用智能指针:这些构造强制执行 RAII(我认为最常见的是 std::shared_ptrstd::unique_ptr)并确保它们负责的内存正确释放。
  2. 视情况而定。如果您使用new 运算符分配对象,然后将它们插入到地图中,但在其他任何地方都没有引用它们,那么您应该手动删除地图的每个条目。它不适用于这种情况,因为映射类型不是指针。

  3. 该类甚至可以省略析构函数声明。如果省略,则编译器会生成析构函数(还有复制赋值运算符、复制构造函数和默认构造函数)。

  4. 已编辑:这取决于,如果您声明了成员A a;,则不必显式删除它,当将其声明为成员的类调用其析构函数时,将调用其析构函数。但如果它是你分配的指针(例如在构造函数中),那么你必须在析构函数中删除它。

  5. 一旦你对一个对象使用动态内存分配,整个对象就在堆上,不管他的成员是如何声明的。

【讨论】:

  • 感谢您的回答。就在第 4 点,我错误地提到了派生/基数,而应该是父/子。刚刚对其进行了编辑。在第 1 点,我同意使用智能指针是更好的方法。但只是想知道为什么进程退出后内存不会被释放。这是操作系统的工作对吗?
  • @user2090833 是的,现代操作系统会这样做,但这仍然是不好的做法。我为您的第 4 个问题编辑了答案。
猜你喜欢
  • 1970-01-01
  • 2013-03-05
  • 1970-01-01
  • 1970-01-01
  • 2016-11-26
  • 1970-01-01
  • 1970-01-01
  • 2012-06-02
  • 2017-12-11
相关资源
最近更新 更多