在接收动态数组或堆对象时,我应该使用 delete 还是 delete[]
Should I use delete or delete[] when receiving either a dynamic array or heap object?
假设我在堆上有两个项目:
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[]
分配的,因此该函数如何正确delete
或delete[]
?
听起来您对单一责任原则有问题。
您有一定数量的处理对单个对象进行操作,因此传递单个对象或数组是合理的。 (为什么该处理对第一个数组元素或非动态对象以外的数组元素也没有用?
然后你必须释放对象。 或者数组。
这里有三个不同的任务,需要三个不同的功能。
从本质上讲,如果"函数不知道其参数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::vector
或 boost::scoped_ptr
、boost::scoped_array
等,它们保证调用正确版本的delete
运算符,正如它们的名字所示。
你必须通过它。 该函数无法在运行时确定这一点。
有关很好的解释,请参阅此处。
编译器供应商决定如何实现,因此属于"魔术"类别。
但一般来说,当您使用 new[] 时,运行时会额外分配 4 个字节。然后它将数组的大小存储在这些字节中,并返回分配 + 4 个字节。解除分配时,它将从您传递它的指针中删除 4,读取大小并使用它来决定要调用多少析构函数等。
您可以在C++常见问题中阅读有关此内容的更多信息
- 我应该使用什么来代替void作为变体中的替代类型之一
- boost::asio::steady_timer()与sleep()我应该使用哪一个
- 我应该实现右值推送功能吗?我应该使用std::move吗
- 我是C++编程的新手,这些代码之间有什么区别,我应该使用哪一个
- 我应该删除矢量<short>吗?
- 我应该如何修改此代码以使用给定字符串中的字母打印菱形图案
- 我应该在锁定TBitmap画布后解锁它吗
- 为什么我应该在异常处理中使用std::cerr而不是std::cout
- 我应该避免多重实现继承吗
- 为了方便起见,我应该避免公开私有字段变量吗
- 我收到同义重复编译器错误。我应该如何修复"类型"X"的参数与类型"X"的参数不兼容?
- 违反const正确性:我应该现实地期待什么问题
- 我应该如何表示我拥有的连续元素序列?
- 我应该将除 .cpp 以外的其他文件添加到 git 中吗?
- 我应该如何从 stdin C++ 中读取可变长度的格式字符串?
- 我有一个对象,它将在整个程序的持续时间内实例化,但一个类成员不会,我应该动态分配它吗?
- 我应该如何捕捉out_of_range异常?
- 我应该在函数中使用 delete[] 吗?
- 在接收动态数组或堆对象时,我应该使用 delete 还是 delete[]
- 当在全局命名空间中覆盖new/delete时,我应该使用std-rtl中的new/delete的默认实现