如何在设计实现级别

时间:2016-03-07 12:06:03

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

几天前我在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不会造成任何内存泄漏?任何人都可以进一步分析这个例子吗?

提前致谢。

4 个答案:

答案 0 :(得分:2)

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

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

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

答案 1 :(得分:1)

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

现在,当您在super*上调用指向sub的删除时,sub的析构函数未被调用,泄露其资源。

如果从中派生的任何类具有任何要解除分配的资源,则始终将基类析构函数声明为虚拟。

答案 2 :(得分:0)

这是经典内存泄漏。经典的内存泄漏会产生内存分配而不会释放。您的新/删除对实际上匹配。

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

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

答案 3 :(得分:0)

如果您要求设计级的解决方案,讨论可以变得非常广泛 而且我相信你并不真正讨论如何实现析构函数。

关于如何避免内存泄漏的设计级别讨论&#39;已经由C ++字段中的大多数专家完成了:StroustrupSutter

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