C++ 带delete[]的析构函数问题

C++ 带delete[]的析构函数问题,c++,memory,valgrind,new-operator,delete-operator,C++,Memory,Valgrind,New Operator,Delete Operator,对于我正在编写的一个程序,我编写了一个简单的数组包装器类(想法是它应该是固定大小的。我知道我可以只使用std::vectors) 我在删除数组时遇到了一个问题 这些是我的构造器: template <class T> Array<T>::Array(size_t size): m_data(new T[size]), m_size(size) {} template <class T> Array<T>::Array(const std::vect

对于我正在编写的一个程序,我编写了一个简单的数组包装器类(想法是它应该是固定大小的。我知道我可以只使用std::vectors) 我在删除数组时遇到了一个问题
这些是我的构造器:

template <class T>
Array<T>::Array(size_t size): m_data(new T[size]), m_size(size)
{}
template <class T>
Array<T>::Array(const std::vector<T> &vec): m_size(vec.size())
{
    std::allocator<T> a;
    m_data = a.allocate(m_size);
    for(int i = 0; i<m_size; i++)
    {
        new (m_data+i) T(vec[i]);
    }
}
模板
数组::数组(大小):m_数据(新的t[size]),m_大小(大小)
{}
模板
数组::数组(const std::vector&vec):m_size(vec.size())
{
分配器a;
m_数据=分配(m_大小);

对于(int i=0;i您正在混合
new
delete[]
,这是未定义的行为。
std::allocator::allocate
使用
运算符new
,而不是
运算符new[]
,因此您必须在其上调用
delete

由于您正在混合如何分配存储,因此需要跟踪分配存储的方式,以便正确地取消分配存储

通过将
std::vector
作为类的数据成员,可以避免所有这些

template <class T>
Array<T>::Array(size_t size): m_data(size) {}

template <class T>
Array<T>::Array(const std::vector<T> &vec): m_data(vec) {}
模板
数组::数组(大小):m_数据(大小){
模板
数组::数组(const std::vector&vec):m_数据(vec){

你不必像向量那样存储大小成员,这是一个好处。

你混合了
new
delete[]
,这是一种未定义的行为。
std::allocator::allocate
使用
操作符new
,而不是
操作符new[]
,因此你必须在上面调用
delete

由于您正在混合如何分配存储,因此需要跟踪分配存储的方式,以便正确地取消分配存储

通过将
std::vector
作为类的数据成员,可以避免所有这些

template <class T>
Array<T>::Array(size_t size): m_data(size) {}

template <class T>
Array<T>::Array(const std::vector<T> &vec): m_data(vec) {}
模板
数组::数组(大小):m_数据(大小){
模板
数组::数组(const std::vector&vec):m_数据(vec){

你可以不必像向量那样存储大小成员。

如果没有使用
new[]
初始化内存,就不能使用
delete[]
进行删除。这是一种(非常非常,非常很少的情况)直接调用对象的析构函数是正确的:

template <class T>
Array<T>::~Array()
{
    for(size_t i = 0; i < m_size; i++) {
        m_data[i].~T();
    }
    allocator.deallocate(m_data);
}
  • 我还要确保您避免使用
    int
    来处理任何涉及此数组大小的内容。有一种说法是,您更喜欢
    int64\u t
    而不是
    size\u t
    (尽管这与STL的行为方式不同,这是一个需要谨慎做出的决定),但您肯定不想限制自己使用带符号的32位数字或更小的数字(在大多数环境中,
    int
关于不使用
删除
的选择:

<代码>删除< /代码>期望将指针传递给它是它自己的离散分配的对象。使用布局-<代码>新< /代码>创建的对象不按此方式分配。请考虑以下内容:

struct point {
    int32_t x, y;
};

int main() {
    char memory[1024];
    size_t size = 128;
    point * arr = reinterpret_cast<point*>(memory);
    for(size_t i = 0; i < size; i++) {
        new(memory + i) point();
    }
    for(size_t i = 0; i < size; i++) {
        arr[i].x = 5;
        arr[i].y = 10;
    }
    for(size_t i = 0; i < size; i++) {
        //undefined behavior, we're deleting objects that weren't allocated on the heap!
        delete (arr + (size - i - 1));
    }
}
即使在这个内存被分配到堆上的情况下,我们仍然会删除代码没有明确分配的指针。这是语言所禁止的,很容易看出原因:对原始内存的一次释放会擦除分配的整个内存块,不需要释放单个内存块。

struct point {
    int32_t x, y;
};

int main() {
    char * memory = new char[1024];
    size_t size = 128;
    point * arr = reinterpret_cast<point*>(memory);
    for(size_t i = 0; i < size; i++) {
        new(memory + i) point();
    }
    for(size_t i = 0; i < size; i++) {
        arr[i].x = 5;
        arr[i].y = 10;
    }
    for(size_t i = 0; i < size; i++) {
        //Still UB: only the first object is a discrete allocation, the rest are part of
        //that original allocation. This attempts to deallocate the same chunk of memory 128 times
        //delete (arr + (size - i - 1));

        //This is correct
        arr[size - i - 1].~point();
    }
    //We do need to deallocate the original memory allocated, but we only do this once.
    delete[] memory;
}
结构点{
int32_t x,y;
};
int main(){
字符*内存=新字符[1024];
尺寸=128;
点*arr=重新解释投射(内存);
对于(大小i=0;i
如果未使用
new[]
初始化内存,则无法使用
delete[]
进行删除。这是一种(非常、非常、非常少数情况)直接调用对象的析构函数是正确的:

template <class T>
Array<T>::~Array()
{
    for(size_t i = 0; i < m_size; i++) {
        m_data[i].~T();
    }
    allocator.deallocate(m_data);
}
  • 我还要确保您避免使用
    int
    来处理任何涉及此数组大小的内容。有一种说法是,您更喜欢
    int64\u t
    而不是
    size\u t
    (尽管这与STL的行为方式不同,这是一个需要谨慎做出的决定),但您肯定不想限制自己使用带符号的32位数字或更小的数字(在大多数环境中,
    int
关于不使用
删除
的选择:

<代码>删除< /代码>期望将指针传递给它是它自己的离散分配的对象。使用布局-<代码>新< /代码>创建的对象不按此方式分配。请考虑以下内容:

struct point {
    int32_t x, y;
};

int main() {
    char memory[1024];
    size_t size = 128;
    point * arr = reinterpret_cast<point*>(memory);
    for(size_t i = 0; i < size; i++) {
        new(memory + i) point();
    }
    for(size_t i = 0; i < size; i++) {
        arr[i].x = 5;
        arr[i].y = 10;
    }
    for(size_t i = 0; i < size; i++) {
        //undefined behavior, we're deleting objects that weren't allocated on the heap!
        delete (arr + (size - i - 1));
    }
}
即使在这个内存被分配到堆上的情况下,我们仍然会删除代码没有明确分配的指针。这是语言所禁止的,很容易看出原因:对原始内存的一次释放会擦除分配的整个内存块,不需要释放单个内存块。

struct point {
    int32_t x, y;
};

int main() {
    char * memory = new char[1024];
    size_t size = 128;
    point * arr = reinterpret_cast<point*>(memory);
    for(size_t i = 0; i < size; i++) {
        new(memory + i) point();
    }
    for(size_t i = 0; i < size; i++) {
        arr[i].x = 5;
        arr[i].y = 10;
    }
    for(size_t i = 0; i < size; i++) {
        //Still UB: only the first object is a discrete allocation, the rest are part of
        //that original allocation. This attempts to deallocate the same chunk of memory 128 times
        //delete (arr + (size - i - 1));

        //This is correct
        arr[size - i - 1].~point();
    }
    //We do need to deallocate the original memory allocated, but we only do this once.
    delete[] memory;
}
结构点{
int32_t x,y;
};
int main(){
字符*内存=新字符[1024];
尺寸=128;
点*arr=重新解释投射(内存);
对于(大小i=0;i