C++ 是否足以在数组后清理 delete[]?

C++ Is delete[] enough to clean up after an array?

在下面的代码中,我使用 new 关键字动态分配一个数组。在 运行 超出范围之前,我在我的数组上调用 delete[],然后我将下注者设置为 null。

我的问题是,如果这足够了,delete[] 是否会确保释放我数组中所有 3 个 Car 对象的分配内存。还是我必须做一些特定的事情来释放每个对象使用的内存?

void main()
{
    Car * myArray = new Car[]{ * new Car("BMW"),*new Car("AUDI"), * new Car("SKODA") };

    delete[] myArray;
    myArray = nullptr;
}

另外,车子class长得像这样。是不是这里把name设置为null也够了。名称是一个字符指针。或者可能不需要将指针设置为 null,因为它没有引用堆上的任何内容。

Car::Car(char * newName)
{
    name = newName;
}

Car::~Car()
{
    name = nullptr;
}

编辑:

首先,感谢所有精彩的回答和评论。我从中学到了很多。

现在我明白了,我需要在声明动态分配数组时指定一个大小。

除此之外,我也明白,我需要尽可能多地停止使用 new。我想最好将对象扔到堆栈上,然后让它们在某个时候超出范围。除此之外,我想我车上的析构函数什么也没做。

阅读评论和答案后,我将代码更改为:

int main()
{
    Car * myArray = new Car[3]{ Car("BMW"), Car("AUDI"), Car("SKODA") };

    delete[] myArray;
    myArray = nullptr;
}

在您的情况下,您已经从对 new Car("BMW") 的调用中泄漏了内存,并且丢失了能够释放内存的指针。

这一行:

Car * myArray = new Car[]{ * new Car("BMW"),*new Car("AUDI"), * new Car("SKODA") };

创建一个包含 3 个 Car 对象的数组,然后为每个条目创建一个新的 Car 对象,用它来初始化数组中的对象,然后忘记新对象。

可以更简单的写成这样:

Car * myArray = new Car[3]{ Car("BMW"), Car("AUDI"), Car("SKODA") };

甚至

Car * myArray = new Car[3]{ "BMW", "AUDI", "SKODA" };

在这种情况下,您的 delete[] 足以释放所使用的内存。

注意

Car::~Car()
{
    name = nullptr;
}

不做任何释放内存的事情。一旦 Car 析构函数被调用,任何人都不应该再次访问该对象的 name(实际上这是未定义的行为),因此将其设置为 null 没有什么意义。

编辑注意:正如 R Sahu 和 Aslak Berby 所指出的,Car * myArray = new Car[]{ ... }; 不是制作数组的有效方法,请改用 Car * myArray = new Car[3]{ ... };

是的,仅当您创建 Car 元素的普通数组时才足够,因为数组名称是指向其第一个元素的指针

您通过指定 []

来通知编译器它是一个数组

在您的情况下,您似乎正在创建汽车指针,因此您必须清理每辆汽车占用的内存位置,然后清理为整个数组分配的内存。

你错误地试图做的是这个,但不要这样做。好复杂

Car** cararrptr = new Car*[3];
cararrptr[0] = new Car("win");
cararrptr[1] = new Car("lin");
cararrptr[2] = new Car("ios");

//now to clean up
delete cararrptr[0];
delete cararrptr[1];
delete cararrptr[2];
delete[] cararrptr;

看看这个讨论

delete[] an array of objects

您不能使用:

Car * myArray = new Car[]{ * new Car("BMW"),*new Car("AUDI"), * new Car("SKODA") };

您需要指定尺寸。

Car * myArray = new Car[3]{ * new Car("BMW"),*new Car("AUDI"), * new Car("SKODA") };

即使在那之后,调用

delete [] myArrary;

将要泄漏内存。该行相当于:

Car * myArray = new Car[3];
Car* car1 = new Car("BMW");
Car* car2 = new Car("AUDI");
Car* car3 = new Car("SKODA");

myArray[0] = *car1;
myArray[1] = *car2;
myArray[2] = *car3;

delete [] myArrary;

不删除为car1car2car3单独分配的对象。您也必须明确删除它们。

delete car3;
delete car2;
delete car1;

但是,您不能使用

Car * myArray = new Car[3];

因为 Car 没有默认构造函数。您可以将默认构造函数添加到 Car。如果失败,您可以使用:

Car * myArray = new Car[3]{ Car("BMW"), Car("AUDI"), Car("SKODA") };

那么,使用就够了:

delete [] myArrary;

释放内存。

基本上每个 new 都需要一个 delete(或 delete[])。但在更复杂的程序中,这可能很难(并且容易出错)确保。

你应该学会使用像 shared_ptr or unique_ptr 这样的智能指针而不是原始指针。在大多数情况下,这些可以让您避免显式 newdelete

Car * myArray = new Car[X];

此代码已创建 X 个汽车对象。您所要做的就是真正使用它们..

但是,我认为您的困惑在于:这是另一种方法

Car ** myArray = new Car*[3] { new Car("BMW"), new Car("AUDI"), new Car("SKODA") };

for (int i = 0; i < 3; i++)
    delete myArray[i];

delete[] myArray;

此代码分配一个包含 3 个 Car* 指针的数组。因此,您尚未创建任何 Car 对象,这就是为什么您使用 new Car() 调用初始化每个 Car* 指针,这实际上创建了 Car 对象。

我同意您过多使用关键字 new 的评论。

我建议改用 std::vector

这是一个工作示例,其中 class Garage 在 heap/freestore 上有一个 Car 的向量,然后使用析构函数 [=15= 删除它]

示例仅供说明:

class Car {
    Car();
    string m_name;
public:
    Car(const string &);
    void print();
};

Car::Car(const string &s) : m_name{s} { }

void Car::print()
{
    cout << m_name << endl;
}

class Garage {
    string m_name;
    vector<Car> *m_cars; // for allocating on heap
public:
    Garage(const string &);
    ~Garage();

    void add(const Car &);
    void print();
};

// Creating a new vector on heap
Garage::Garage(const string &s) : m_name{s}, m_cars{new vector<Car>} { }

Garage::~Garage()
{
    delete m_cars; // deleting the vector.
}

void Garage::add(const Car &c)
{
    m_cars->push_back(c);
}

void Garage::print()
{
    for (auto car : *m_cars)
        car.print();
}

int main()
{
    Garage garage{"Luxury garage"};
    garage.add(Car("BMW"));
    garage.add(Car("Audi"));
    garage.add(Car("Skoda"));

    garage.print();
}

上面使用new vector只是为了演示,不需要。为此,使用不带 new 的 std::vector 更快更安全,并且您无需在使用后删除它。

同时考虑使用智能指针而不是 new

您可以将对象添加到堆或栈中。如果将它添加到堆中,则可以随时动态创建它。这是使用 new 完成的,你会在 return.

中得到一个指针
Car *aCar=new Car("BMW");

如果您在堆栈上创建它,您只需像定义其他变量一样定义它。

Car anotherCar("BMW");

如果在堆上创建,还需要从堆中释放。这是通过删除完成的。

delete aCar;

您永远不会释放在堆栈上创建的对象。当您超出范围时,它将自动解除分配。

至于创建数组,您可以创建静态或动态对象数组。

动态:

Car **cars=new Car*[3];
cars[0]=new Car("BMW");
cars[1]=new Car ....

所有这些都需要单独删除。这里没有级联。

delete cars[0];
delete cars[1];
// And finaly the array.
delete[] cars;

您可以稳定地创建它们:

Car cars[]={"BWM", "AUDI"};

这次包括数组在内的所有对象都被压入栈中,当你超出范围时将被删除。

在这两者之间,您可以创建指向堆分配对象的基于堆栈的数组,或此处其他建议的堆分配静态数组。

至于 C++,我建议使用 std:: 库,在这种情况下 std::vector;

或者:

std::vector<Car *> myArray;
myArray.push_back(new Car("BMW"));

.....
// Cleanup:
for(auto car : myArray)
     delete car;

或者:

Car car;    
std::vector<Car> myArray;

car.SetType("BMW");
myArray.push_back(std::move(car));

我不确定我是怎么和一个两岁的孩子在一起的 post。 None 的答案确实给出了一个简单的 C++ 解决方案,所以这里是一个使用容器的 60 秒解决方案,看不到 new/delete。

#include <iostream>
#include <string>
#include <vector>

struct Car {
  // Allows implicit conversion from char *
  Car(const char *manufacturer) : manufacturer_(manufacturer) {}
  std::string manufacturer_;
};

int main() {
  std::vector<Car> cars{ "BMW", "AUDI", "SKODA" };

  for (const auto& car : cars) {
    std::cout << car.manufacturer_ << "\n";
  }
}

live demo

不。 delete []myArray 只会导致称为 悬挂指针的东西.

这基本上意味着程序不再拥有指向的内存 myArray,但是 myArray 仍然指向那个内存。

为避免悬空指针,请在删除后将指针分配给 nullptr

delete[]myArray;

myArray = nullptr;