在分配为不同类型的数组上使用 delete[] 是否安全?
Is it safe to use delete[] on an array that was allocated as a different type?
为了使用 placement new 而不是自动尝试调用默认构造函数,我使用 reinterpret_cast<Object*>(new char[num_elements * sizeof(Object)])
而不是 new Object[num_elements]
分配数组.
但是,我不确定应该如何删除数组以便正确调用析构函数。我是否应该遍历元素,为每个元素手动调用析构函数,然后将数组转换为 char*
并在其上使用 delete[]
,如下所示:
for (size_t i = 0; i < num_elements; ++i) {
array[i].~Object();
}
delete[] reinterpret_cast<char*>(array);
或者如果我不为每个元素手动调用析构函数就足够了,只是依靠 delete[] 来做到这一点,因为数组的类型是 Object*
,比如 delete[] array
?
我担心的是,并非每个平台都能够以这种方式正确确定数组中的元素数量,因为我没有分配使用大小合适的类型的数组。 An answer 关于 "how delete[] knows the size of the operand" 的问题表明 delete[]
的可能实现是存储分配元素的数量(而不是字节数)。
如果 delete[]
确实以这种方式实现,这表明仅使用 delete[] array
会尝试删除太多元素,因为创建数组时 char
元素多于有多少 Object
个元素适合它。所以在那种情况下,删除数组的唯一可靠方法是手动调用析构函数,将数组转换为 char*
,然后使用 delete[]
.
然而,实现它的另一种合乎逻辑的方法是存储 数组的大小 以字节为单位,而不是元素的数量,然后在调用 delete[]
,用数组的大小除以类型的大小,得到调用析构函数的元素数量。如果使用此方法,则只需使用 delete[] array
,其中 array
的类型为 Object*
就足够了。
所以我的问题是:如果数组最初没有分配正确的类型,我能否依靠 delete[] 正确调用操作数数组中元素的析构函数?
这是我使用的代码:
template <typename NumberType>
NeuronLayer<NumberType>::NeuronLayer(size_t num_inputs, size_t num_neurons, const NumberType *weights)
: neurons(reinterpret_cast<Neuron<NumberType>*>(new char[num_neurons * sizeof(Neuron<NumberType>)])),
num_neurons(num_neurons), num_weights(0) {
for (size_t i = 0; i < num_neurons; ++i) {
Neuron<NumberType> &neuron = neurons[i];
new(&neuron) Neuron<NumberType>(num_inputs, weights + num_weights);
num_weights += neuron.GetNumWeights();
}
}
和
template <typename NumberType>
NeuronLayer<NumberType>::~NeuronLayer() {
delete[] neurons;
}
或
template <typename NumberType>
NeuronLayer<NumberType>::~NeuronLayer() {
for (size_t i = 0; i < num_neurons; ++i) {
neurons[i].~Neuron();
}
delete[] reinterpret_cast<char*>(neurons);
}
这是管理动态对象数组的可移植代码。它本质上是 std::vector
:
void * addr = ::operator new(sizeof(Object) * num_elements);
Object * p = static_cast<Object *>(addr);
for (std::size_t i = 0; i != num_elements; ++i)
{
::new (p + i) Object(/* some initializer */);
}
// ...
for (std::size_t i = 0; i != num_elements; ++i)
{
std::size_t ri = num_elements - i - 1;
(p + ri)->~Object();
}
::operator delete(addr);
这是一般模式,如果您想进行非常低级别的控制,您应该如何组织动态存储。结果是动态数组永远不应该是一种语言特性,而是在库中更好地实现。正如我上面所说,这段代码与名为 std::vector<Object>
.
的现有标准库小工具几乎相同
在 Object*
上调用 delete[]
将为 new[]
分配的每个对象调用一次析构函数。 new Object[N]
通常在实际数组之前存储 N,并且 delete[]
当然知道去哪里查找。
您的代码不存储该计数。它不能,因为它是一个未指定的实现细节,其中以及如何存储计数。正如您推测的那样,有两种明显的方式:元素计数和数组大小,以及一种明显的位置(在数组之前)。即便如此,也可能存在对齐问题,并且您无法预测尺寸使用的是什么类型。
此外,new unsigned char[N]
是一个特例,因为 delete[]
不需要调用 char
的析构函数。在那种情况下 new[]
根本不需要存储 N
。因此,即使 new Object[N]
会存储一个尺寸,您甚至无法确定存储的尺寸。
为了使用 placement new 而不是自动尝试调用默认构造函数,我使用 reinterpret_cast<Object*>(new char[num_elements * sizeof(Object)])
而不是 new Object[num_elements]
分配数组.
但是,我不确定应该如何删除数组以便正确调用析构函数。我是否应该遍历元素,为每个元素手动调用析构函数,然后将数组转换为 char*
并在其上使用 delete[]
,如下所示:
for (size_t i = 0; i < num_elements; ++i) {
array[i].~Object();
}
delete[] reinterpret_cast<char*>(array);
或者如果我不为每个元素手动调用析构函数就足够了,只是依靠 delete[] 来做到这一点,因为数组的类型是 Object*
,比如 delete[] array
?
我担心的是,并非每个平台都能够以这种方式正确确定数组中的元素数量,因为我没有分配使用大小合适的类型的数组。 An answer 关于 "how delete[] knows the size of the operand" 的问题表明 delete[]
的可能实现是存储分配元素的数量(而不是字节数)。
如果 delete[]
确实以这种方式实现,这表明仅使用 delete[] array
会尝试删除太多元素,因为创建数组时 char
元素多于有多少 Object
个元素适合它。所以在那种情况下,删除数组的唯一可靠方法是手动调用析构函数,将数组转换为 char*
,然后使用 delete[]
.
然而,实现它的另一种合乎逻辑的方法是存储 数组的大小 以字节为单位,而不是元素的数量,然后在调用 delete[]
,用数组的大小除以类型的大小,得到调用析构函数的元素数量。如果使用此方法,则只需使用 delete[] array
,其中 array
的类型为 Object*
就足够了。
所以我的问题是:如果数组最初没有分配正确的类型,我能否依靠 delete[] 正确调用操作数数组中元素的析构函数?
这是我使用的代码:
template <typename NumberType>
NeuronLayer<NumberType>::NeuronLayer(size_t num_inputs, size_t num_neurons, const NumberType *weights)
: neurons(reinterpret_cast<Neuron<NumberType>*>(new char[num_neurons * sizeof(Neuron<NumberType>)])),
num_neurons(num_neurons), num_weights(0) {
for (size_t i = 0; i < num_neurons; ++i) {
Neuron<NumberType> &neuron = neurons[i];
new(&neuron) Neuron<NumberType>(num_inputs, weights + num_weights);
num_weights += neuron.GetNumWeights();
}
}
和
template <typename NumberType>
NeuronLayer<NumberType>::~NeuronLayer() {
delete[] neurons;
}
或
template <typename NumberType>
NeuronLayer<NumberType>::~NeuronLayer() {
for (size_t i = 0; i < num_neurons; ++i) {
neurons[i].~Neuron();
}
delete[] reinterpret_cast<char*>(neurons);
}
这是管理动态对象数组的可移植代码。它本质上是 std::vector
:
void * addr = ::operator new(sizeof(Object) * num_elements);
Object * p = static_cast<Object *>(addr);
for (std::size_t i = 0; i != num_elements; ++i)
{
::new (p + i) Object(/* some initializer */);
}
// ...
for (std::size_t i = 0; i != num_elements; ++i)
{
std::size_t ri = num_elements - i - 1;
(p + ri)->~Object();
}
::operator delete(addr);
这是一般模式,如果您想进行非常低级别的控制,您应该如何组织动态存储。结果是动态数组永远不应该是一种语言特性,而是在库中更好地实现。正如我上面所说,这段代码与名为 std::vector<Object>
.
在 Object*
上调用 delete[]
将为 new[]
分配的每个对象调用一次析构函数。 new Object[N]
通常在实际数组之前存储 N,并且 delete[]
当然知道去哪里查找。
您的代码不存储该计数。它不能,因为它是一个未指定的实现细节,其中以及如何存储计数。正如您推测的那样,有两种明显的方式:元素计数和数组大小,以及一种明显的位置(在数组之前)。即便如此,也可能存在对齐问题,并且您无法预测尺寸使用的是什么类型。
此外,new unsigned char[N]
是一个特例,因为 delete[]
不需要调用 char
的析构函数。在那种情况下 new[]
根本不需要存储 N
。因此,即使 new Object[N]
会存储一个尺寸,您甚至无法确定存储的尺寸。