C++指针数组:delete还是delete[]?

66

考虑以下代码:

class Foo
{
    Monster* monsters[6];

    Foo()
    {
        for (int i = 0; i < 6; i++)
        {
            monsters[i] = new Monster();
        }
    }

    virtual ~Foo();
}

正确的析构函数是什么?

这个:

Foo::~Foo()
{
    delete [] monsters;
}

或者这样:

Foo::~Foo()
{
    for (int i = 0; i < 6; i++)
    {
        delete monsters[i];
    }
}

目前我有最上层的构造函数,一切都运行良好,但是当然我看不到是否会有泄漏发生...

个人认为第二个版本更加符合我的需求。无论如何,这个操作的“正确”方式是什么?

8个回答

73

delete[] monsters;

这是不正确的,因为monsters不是指向动态分配数组的指针,而是一个指针数组。作为类成员,它将在类实例被销毁时自动销毁。
你的另一种实现是正确的,因为数组中的指针确实指向动态分配的Monster对象。
请注意,根据你当前的内存分配策略,你可能需要声明自己的拷贝构造函数和拷贝赋值运算符,以防止无意中复制导致双重删除。(如果你想防止复制,可以将它们声明为私有,并且实际上不实现它们。)

请问您能否解释一下为什么应该禁用复制构造函数和复制赋值运算符? - gen
@gen 这只是一个猜测,所以它的价值取决于你的判断力,但我认为如果没有自定义的复制赋值/构造函数,以下情况可能会发生:有两个Foo对象,它们都有一个指向相同怪物的巨大指针数组。如果你销毁Foo1,那么Foo2中的指针将无效,因为它们指向的内存已经被Foo1释放了。 - ZeroStatic

55

对于 new,应使用 delete。对于 new[],应使用 delete[]。你的第二个变量是正确的。


16
为了简化回答,让我们看以下代码:
#include "stdafx.h"
#include <iostream>
using namespace std;

class A
{
private:
    int m_id;
    static int count;
public:
    A() {count++; m_id = count;}
    A(int id) { m_id = id; }
    ~A() {cout<< "Destructor A "   <<m_id<<endl; }
};

int A::count = 0;

void f1()
{   
    A* arr = new A[10];
    //delete operate only one constructor, and crash!
    delete arr;
    //delete[] arr;
}

int main()
{
    f1();
    system("PAUSE");
    return 0;
}

输出结果为: 析构函数 A 1 然后程序崩溃了(表达式:_BLOCK_TYPE_IS_VALID(phead- nBlockUse))。

我们需要使用delete[] arr;,因为它删除的是整个数组而不仅仅是一个单元格!

尝试使用delete[] arr; ,输出结果为: 析构函数 A 10 析构函数 A 9 析构函数 A 8 析构函数 A 7 析构函数 A 6 析构函数 A 5 析构函数 A 4 析构函数 A 3 析构函数 A 2 析构函数 A 1

对于指针数组也是同样的原理:

void f2()
{
    A** arr = new A*[10];
    for(int i = 0; i < 10; i++)
    {
        arr[i] = new A(i);
    }
    for(int i = 0; i < 10; i++)
    {
        delete arr[i];//delete the A object allocations.
    }

    delete[] arr;//delete the array of pointers
}

如果我们使用 delete arr 而不是 delete[] arr,它将不会删除数组中的所有指针 => 指针对象的内存泄漏!


14
第二个选项在这种情况下是正确的(好吧,至少是最不错的选择)。 编辑: "最不错",因为原始代码没有充分理由使用newdelete,所以你应该只使用:
std::vector<Monster> monsters;
结果将是更简单的代码和更清晰的职责分离。

1
@FredOverflow:虽然他可能在处理多态层次结构,但是1)他实际上并没有展示出来,2)如果是这样,使用向量仍然没问题。 - Jerry Coffin
2
@Jerry 1) 对的 2) 绝对 - fredoverflow
2
如果Monster是一个基类,并且它不想与其他Foo对象共享其怪物,则应该使用boost::ptr_vector<Monster>,否则使用Monster对象的数组(而不是怪物指针)。 - Martin York
基本上,这里有数据的初始化,之后就不再进行插入或删除操作,直到销毁。然而,由于我唯一访问数据的方式是从n=0循环到n=size,所以我认为性能不会有太大的差别...实际上,据我所知,除非你使用“at”(或具有相同效果的重载运算符),否则与列表相比,并没有什么明显的劣势,对吧? - Jasper
1
你需要同时做两件事情!!delete mosters[i]将删除数组中指向的数据。然后,delete [] monsters将删除实际的数组! - Sellorio
显示剩余16条评论

8

delete[] monsters是绝对错误的。我的堆调试器显示以下输出:

allocated non-array memory at 0x3e38f0 (20 bytes)
allocated non-array memory at 0x3e3920 (20 bytes)
allocated non-array memory at 0x3e3950 (20 bytes)
allocated non-array memory at 0x3e3980 (20 bytes)
allocated non-array memory at 0x3e39b0 (20 bytes)
allocated non-array memory at 0x3e39e0 (20 bytes)
releasing     array memory at 0x22ff38

如您所见,您试图使用错误的删除形式(非数组 vs. 数组),并且指针0x22ff38从未由new调用返回。第二个版本显示了正确的输出:

[allocations omitted for brevity]
releasing non-array memory at 0x3e38f0
releasing non-array memory at 0x3e3920
releasing non-array memory at 0x3e3950
releasing non-array memory at 0x3e3980
releasing non-array memory at 0x3e39b0
releasing non-array memory at 0x3e39e0

总之,我更喜欢一种设计,在这种设计中,不需要手动实现析构函数。

#include <array>
#include <memory>

class Foo
{
    std::array<std::shared_ptr<Monster>, 6> monsters;

    Foo()
    {
        for (int i = 0; i < 6; ++i)
        {
            monsters[i].reset(new Monster());
        }
    }

    virtual ~Foo()
    {
        // nothing to do manually
    }
};

3
您的第二个示例是正确的;您不需要删除monsters数组本身,只需删除您创建的各个对象即可。

2

如果你的代码像这样写,那就更合理:

#include <iostream>

using namespace std;

class Monster
{
public:
        Monster() { cout << "Monster!" << endl; }
        virtual ~Monster() { cout << "Monster Died" << endl; }
};

int main(int argc, const char* argv[])
{
        Monster *mon = new Monster[6];

        delete [] mon;

        return 0;
}

0
你需要逐个删除每个指针,然后再删除整个数组。确保为存储在数组中的类定义了适当的析构函数,否则无法确保对象被正确清理。请确保所有的析构函数都是虚拟的,以便在继承时能够正确地使用它们。

我认为两者都做会相当奇怪,因为数组长度是固定的,所以不需要删除它。此外,我已经声明了析构函数为虚函数,所以那个评论是无用的。 - Jasper

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