new操作符分配的内存是否会自动释放?

5
我99%确定答案是绝对的“不”。请验证我的主张,以下代码会产生内存泄漏。
Data &getData()
{
    Data *i = new Data();
    return *i;
}

void exampleFunc()
{
    Data d1 = getData();
    Data d2;

    /* d1 is not deallocated because it is on the heap, and d2 is
     * because it is on the stack. */
}

请注意,这只是一个过于简化的示例,所以显然你不会实际使用上述代码...所以不需要指出谢谢。
更新1:
补充一下,如果我将指针分配给引用,那么情况怎么样?在这种情况下,我假设数据没有被复制...
Data &getData()
{
    Data *i = new Data();
    return *i;
}

void exampleFunc()
{
    // Does copying occur here?
    Data &d1 = getData();

    // Does this deallocate the memory assigned to the pointer?
    delete &d;
}

更新2:

我想回答我自己在更新1中的问题,以下代码证明将引用分配给另一个引用并不会导致复制...

#include <iostream>
#include <string>

using namespace std;

class Data
{
public:
    string mName;

    Data(const string &name) : mName(name)
    { cout << mName << " default ctor" << endl; }

    Data(const Data& other)
    {
        mName = other.mName + " (copy)";
        cout << mName << " copy ctor" << endl;
    }

    ~Data()
    { cout << mName << " dtor" << endl; }

    static Data &getData(const string &name)
    {
        Data *d = new Data(name);
        return *d;
    }
};

int main()
{
    cout << "d1..." << endl;
    Data d1 = Data::getData("d1");

    cout << "d2..." << endl;
    Data d2("d2");

    cout << "d3..." << endl;
    Data &d3 = Data::getData("d3");

    cout << "return..."  << endl;
    return 0;
}

产生以下结果...
d1...
d1 default ctor
d1 (copy) copy ctor
d2...
d2 default ctor
d3...
d3 default ctor
return...
d2 dtor
d1 (copy) dtor

感谢Eric Melski提供了精彩的回答(我在更新2中的代码是他示例代码的修改副本)。

7个回答

25
实际上,d1d2都将被释放,因为它们都在堆栈上。未被释放的是您在getData()函数中分配的Data对象。如果您使用构造函数和析构函数中的工具来详细说明Data类,您可以更清楚地看到这一点。例如:
class Data {
public:
    Data() { cout << "Data default ctor" << endl; }
    Data(const Data& other) { cout << "Data copy ctor" << endl; }
    ~Data() { cout << "Data dtor" << endl; }

    static Data& getData()
    {
        Data *i = new Data();
        return *i;
    }
};

请注意,我已经明确声明了 Data 的复制构造函数。在您的示例中,当您执行 Data d1 = getData(); 时,您隐式地调用了该构造函数,我怀疑这就是您困惑的原因。现在,如果我运行这个简单的程序:
#include <iostream>
using namespace std;

int main(int argc, char *argv[])
{
    Data d1 = Data::getData();
    Data d2;

    return 0;
}

输出结果如下:

Data default ctor
Data copy ctor
Data default ctor
Data dtor
Data dtor

逐行解释一下你所看到的代码:

  1. getData() 中分配新的 Data 时会调用默认构造函数。
  2. 通过调用复制构造函数创建 d1,它是从你刚刚创建的动态分配的 Data 中创建的。
  3. 调用默认构造函数来创建 d2
  4. d2main() 结束时超出作用域,并且调用了其析构函数。
  5. d1main() 结束时超出作用域,并且调用了其析构函数。

请注意,有三个构造函数调用,但只有两个析构函数调用 - 这表明您泄漏了一个 Data 对象。


8

d1 是一个栈对象,通过从 getData() 返回的引用进行复制构造。 d1 已被释放,但在 getData() 中创建的对象泄漏了。


当你说复制构造时,如果Data上没有接收Data的构造函数会发生什么?会进行深拷贝吗? - Nick Bolton
不,这是一种浅拷贝。也就是说,对象中的任何引用和指针都将指向与原始对象相同的对象。然而,在这种情况下,原始对象(在getData()中创建的对象)仍然会泄漏。 - C. K. Young

3

d1d2都是栈对象,因此将在其作用域结束时被销毁。问题在于getData()创建了一个新的堆对象,该对象从未被删除。 d1从指向此堆对象的引用中进行复制初始化,d1本身将在exampleFunc的结尾正确销毁,但是每次调用getData()时生成的堆对象将不会被删除。

getData()具有的签名下,可以删除此对象,但是返回需要删除的内容的引用不是惯用的接口。

虽然可行,但不是一个好的接口:

Data& d = getData();
delete &d;

3

只有在操作系统在程序终止时自动回收内存时,您才能恢复内存。在程序执行期间,您发布的内容将导致内存泄漏,因为C ++没有内置垃圾收集器,并且需要手动管理堆分配对象的内存。


1

这是Java/C#程序员的典型"思维方式"。

在C++中,您实际上可能会使用比您想象中更多的值类型:

Data getData()
{
    Data i;
    return i;
}

void exampleFunc()
{
    Data d1 = getData();
    /* d1 is constructed here and destroyed */
    Data d2;

}

如果你想返回指针,例如当你有一些派生类只使用智能指针(如auto_ptr)允许所有权移动时:

auto_ptr<Data> getData()
{
    auto_ptr<Data> i(new Data());
    return i;
}

void exampleFunc()
{
    auto_ptr<Data> d1 = getData();
    /* now d1 is destroyed when goes out of scope */
    Data d2;
}

0

你说得对。你必须使用delete或delete[]显式地释放内存。


0
准确来说,你问题的答案是肯定的。
在这段代码中:
int main()
{
Data* d = new Data();
return 0;   //end of execution
}

d所指向的数据在执行结束时会被操作系统自动释放。

换句话说,所有由程序分配但在程序执行完成后未被释放的数据,都将在执行结束后由操作系统释放。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接