在接收动态数组或堆对象时,我应该使用 delete 还是 delete[]

Should I use delete or delete[] when receiving either a dynamic array or heap object?

本文关键字:delete 我应该 还是 动态 数组 对象      更新时间:2023-10-16

假设我在堆上有两个项目:

Foo *f = new Foo;
Foo *g = new Foo[42];

假设我有一个接收Foo指针的函数,并且在函数中它需要执行delete

void bar(Foo *p) {
    // some stuff
    delete p;
}

可以这样调用此函数:

bar(f); // passing a pointer to a Foo object on the heap
bar(g); // passing a pointer to an array on the heap

我认识到delete[]delete应该分别用于释放分配new[]new的内存;但是,由于该函数不知道其参数p是用new还是new[]分配的,因此该函数如何正确deletedelete[]

听起来您对单一责任原则有问题。

您有一定数量的处理对单个对象进行操作,因此传递单个对象或数组是合理的。 (为什么该处理对第一个数组元素或非动态对象以外的数组元素也没有用?

然后你必须释放对象。 或者数组。

这里有三个不同的

任务,需要三个不同的功能。

从本质上讲,如果"函数不知道其参数p是分配了new还是new[]",则该函数没有业务尝试解除分配。 如果参数在堆栈上怎么办? 如果使用池分配器会怎样?

此外,一旦处理被移动到一个单独的函数,就可以很容易地创建一个"处理然后删除单个对象"和"处理然后删除数组"的函数,而不会重复(两者都调用处理组件的帮助程序函数)。

您无法(可移植地)检测到并在函数中执行正确的操作。

一种"解决方法"是使用 std::vector<Foo> 而不是数组,并且始终使用 delete

解决方案 1。从代码设计的角度来看,您可能应该完全避免这种情况,并让分配它的同一类、一对创建-销毁函数或代码块删除该对象。这样,您就知道自己没有犯任何错误。

解决方案 2.如果编译器支持它们,请使用 std::shared_ptr 和 lambda 函数,并根据需要为每个指针使用不同的解分配器。在此处阅读有关共享指针的信息。

std::shared_ptr<Foo> f(new Foo[20], [](Foo* p) { delete[] p; });
std::shared_ptr<Foo> g(new Foo);

第一个,f是指向数组的共享指针。当我创建它时,我给第一个参数一个指向数组的(普通)指针;第二个参数是一个 lambda 函数,它采用一个 Foo* p 参数,其主体为 { delete[] p; }

默认情况下,std::shared_ptr使用 delete 来释放内存。这就是为什么当我创建第二个时,我只给它指向对象的指针,而不指定任何自定义释放器。

你不能。你永远不知道指针是指向一个对象还是指向一个这样的对象的数组。

但是,如果你

真的想有一个函数来解除分配,如果你能记住分配任何类似数组的东西,即使你分配了一个对象,以下用法是合法的:

Foo *h = new Foo[0];
Foo *f = new Foo[1];
Foo *g = new Foo[42];
void bar(Foo *p) {
    // some stuff
    delete [] p;
}

但是,请记住,有时技术上可行并不意味着您应该使用它。这取决于您的用例是否有意义,将对象视为数组的特殊情况。

更优雅C++方法是使用 std::vectorboost::scoped_ptrboost::scoped_array 等,它们保证调用正确版本的delete运算符,正如它们的名字所示。

你必须通过它。 该函数无法在运行时确定这一点。

有关很好的解释,请参阅此处。

编译器供应商决定如何实现,因此属于"魔术"类别。

但一般来说,当您使用 new[] 时,运行时会额外分配 4 个字节。然后它将数组的大小存储在这些字节中,并返回分配 + 4 个字节。解除分配时,它将从您传递它的指针中删除 4,读取大小并使用它来决定要调用多少析构函数等。

您可以在C++常见问题中阅读有关此内容的更多信息