在分配为不同类型的数组上使用delete []是否安全?

时间:2015-04-12 18:39:27

标签: c++ arrays destructor delete-operator 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关于“delete []如何知道操作数的大小”的问题“建议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);
}

2 个答案:

答案 0 :(得分:2)

delete[]上调用Object*将为new[]分配的每个对象调用析构函数一次。 new Object[N]通常在实际数组之前存储N,而delete[]肯定知道在哪里查看。

您的代码不存储该计数。它不能,因为它是一个未指定的实现细节,存储计数的位置和方式。正如您推测的那样,有两种显而易见的方法:元素计数和数组大小,以及一个明显的位置(在数组之前)。即便如此,也可能存在对齐问题,并且无法预测大小所使用的类型。

此外,new unsigned char[N]是一种特殊情况,因为delete[]不需要调用char的析构函数。在这种情况下,new[]根本不需要存储N。因此,即使new Object[N]存储了大小,您甚至无法存储该大小。

答案 1 :(得分:1)

这是管理动态对象数组的可移植代码。它基本上是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>的现有标准库小工具几乎完全相同。