【问题标题】:How to avoid memory leak at design-implementation level如何在设计实现级别避免内存泄漏
【发布时间】:2016-06-20 23:28:50
【问题描述】:

几天前我看到一个关于 C++ 内存泄漏的面试问题。 代码是这样的(如果我没记错的话):

#include <iostream>
using namespace std;

class super {
    int value;
    int arr[1000];
public:
    super() :value(0) {}
    super(int value) :value(value) {}
    virtual int getValue() const{
        return this->value;
    }
};

class sub : public super {
    int val;
    super sup;
    vector<int> v1;
public:
    sub() :val(0), sup(0) {}
    sub(int value) :val(value), sup(value), v1(10,0) {}
    int getValue() const{
        return this->val;
    }
};

int main() {
    sub* pt1 = new(sub);
    super* pt2 = pt1;

    pt1 = new(sub);

    delete pt2;     //memory leak ??

    //more code here...
    delete pt1;
    return 0;
}

问题是如何在实现设计级别避免这种类型的内存泄漏。我想这个问题不仅仅是简单地回答“不要使用那样的指针”。

它是否与将析构函数实现为虚拟或使用动态转换有关?我们如何实现析构函数以使delete pt2 不会造成任何内存泄漏?谁能进一步分析这个例子?

提前致谢。

【问题讨论】:

  • 我不明白这是如何泄漏内存的。您正在通过指向其基类的指针删除第一个 sub 对象,这应该没问题,因为 sub 不包含任何资源。如果是这样,您必须在 super 中声明一个虚拟析构函数。
  • 我猜这里缺少虚拟析构函数见stackoverflow.com/questions/461203/…
  • @RolandW 是的,我不记得该示例的确切代码。随意编辑问题中的代码,以便子类可以保存资源。
  • 只需将ar 更改为向量即可。当将 sub 转换为 super 并删除它时,super dtor 不会调用 sub dtor 不会调用不会释放动态内存的 ar/vector dtor -> 内存泄漏。
  • @Youka 现在可以了吗?

标签: c++ object inheritance memory-management memory-leaks


【解决方案1】:

首先,delete pt2; 并不是专门的内存泄漏。我最初说这是未定义的行为,因此标准允许任何事情,包括内存泄漏。但是在仔细检查这些类之后,实际上对于带有 int 数组的代码的第一个版本,sub 可以简单地破坏,因此,尽管看起来很奇怪,这段代码是正确的。然后您更改了代码以使 sub 不再可轻易破坏(由于 vector 数据成员),因此它现在是未定义的行为。

提出问题的人可能一直在寻找一个具体的答案,如果是这样,我不知道那是什么,但这个错误可能以不止一种方式被“设计掉”:

  1. super 的析构函数设为虚拟,这样就可以通过父指针删除sub。一个常见的经验法则是,具有虚函数的类应始终具有虚析构函数,因为此类类旨在通过对基类的指针/引用来使用。
  2. 在用户代码中,使用 RAII 技术确保正确销毁。在这种情况下,智能指针。 shared_ptr 允许您编写 shared_ptr&lt;super&gt; pt2(new sub()); 并且即使使用非虚拟析构函数,该对象也将被正确删除,尽管有些人认为该功能晦涩难懂。
  3. 很难将此称为“实施设计”决策,因为“不要犯严重的编码错误”不是设计规则,而是语言规则。但是调用代码可以“设计”为不通过指向其基类的指针删除对象,除非这样做是有效的(对于这个类来说不是这样)。类可以通过一致的文档来帮助解决这个问题。
  4. supersub 类中,使用vector&lt;int&gt; 而不是普通的int 数组。这使得对象本身更小,将大部分数据存储在外部,因此对于像这个示例这样的用途,用户不会觉得他们必须动态分配对象,而是可以将它们放在堆栈上(不一定是 1000 个整数 不能进入堆栈,只是它的大小可能会让用户感到紧张)。因此,如果您对super 进行与对sub 所做的相同更改,那么调用代码可以通过尽可能使用自动变量而不是动态分配作为设计原则来降低此类错误的风险。

【讨论】:

  • 我想我把问题搞砸了。我看到的问题肯定是内存泄漏问题,但是这里我是按内存写的,所以可能有点错误。
【解决方案2】:

不是典型的内存泄漏。经典的内存泄漏将有内存分配和没有释放。您的新/删除对实际上匹配。

这是未定义的行为。你看到了一个相当冗长的解释 here 为什么有一个 public 而不是virtual 析构函数会产生问题。

使用他们的特定的编译器,这可能会泄漏内存,因为编译器在面对未定义的行为时已尽其所能保持理智。另一个编译器或其编译器的另一个版本可能会炸毁或创建粉红色的跳舞独角​​兽。这不是内存泄漏的一个很好的例子,因为内存泄漏是编译器在更糟糕的情况下所能产生的最好结果。

【讨论】:

    【解决方案3】:

    如果您要求设计级别的解决方案,讨论范围会非常广泛。
    而且我相信你并没有真正讨论如何实现析构函数。

    关于如何避免“内存泄漏”的设计级别讨论已经由 C++ 领域的大多数专家完成:StroustrupSutter

    我建议观看他们的演示视频并阅读他们的文章。

    【讨论】:

      【解决方案4】:

      它会泄漏内存,因为super 没有虚拟析构函数 (virtual ~super() = default;)。

      现在,当您在指向 subsuper* 上调用 delete 时,不会调用 sub 的析构函数,从而泄漏其资源。

      如果从基类派生的任何类有任何资源要释放,则始终将基类析构函数声明为虚拟。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2020-05-13
        • 1970-01-01
        • 2018-04-08
        • 2013-06-24
        • 1970-01-01
        • 2016-08-14
        相关资源
        最近更新 更多