动态记忆的删除是如何真正起作用的

How does deletion of dynamic memory really work?

本文关键字:何真正 起作用 删除 动态记忆      更新时间:2023-10-16

我主要有两种类型的对象:Person和Pet。一个人可以养很多宠物。我正在动态地创建这两种类型的对象。

#include <iostream>
#include <string>
#include <vector>
struct Pet
{
Pet() { }
Pet(std::string name, std::string type)
{
Name = name;
Type = type;
std::cout << Name << " is created" << std::endl;
}
~Pet()
{
std::cout << Name << " is destroyed" << std::endl;
}
std::string GetName() { return Name; }
std::string GetType() { return Type; }
std::string Name;
std::string Type;
};
struct Person
{
Person(std::string name)
{
Name = name;
std::cout << Name << " is created" << std::endl;
}
~Person()
{
std::cout << Name << " is destroyed" << std::endl;
}
void AddPet(Pet& pet)
{
Pets.push_back(pet);
}
std::string GetPersonsPets()
{
if (Pets.size() == 0)
{
return Name + " has no pets";
}
if (Pets.size() == 1)
{
return Name + " has " + Pets[0].GetName() + ",a " + Pets[0].GetType();
}
}
std::string Name;
std::vector<Pet> Pets;
};
int main()
{
Person* peter = new Person("Peter");
Person* alice = new Person("Alice");
Pet* fluffy = new Pet("Flyffy", "dog");
peter->AddPet(*fluffy);
std::vector<Person*> People;
People.push_back(peter);
People.push_back(alice);
int i = 0;
for (std::vector<Person*>::iterator it = People.begin(); it != People.end();
it++)
{
std::cout << People[i]->GetPersonsPets() << std::endl;
i++;
}
//delete fluffy;
delete alice;
delete peter;
}

一切似乎都正常工作,但当涉及到删除宠物对象时,会发生一些有趣的事情。当我取消对delete fluffy的注释时,floffy会在peter之后自动删除。

我想,既然蓬松是动态创建的,除非我自己做,否则它永远不会被破坏?

但当我不取消对delete fluffy的注释时,会发生另一件有趣的事情。然后它的析构函数将被调用两次。

怎么可能两次毁掉一件东西?

在此处添加宠物时:

peter->AddPet(*fluffy);

则CCD_ 3是被引用的指针

void Person::AddPet(Pet& pet)
{
Pets.push_back(pet);
}

您将*fluffy的副本存储在某个容器中(我想它是std::vector(。

你看到的被破坏的对象是那些副本(向量管理自己的内存,即当向量被破坏时,它会破坏其元素(。您仍然需要删除在main中创建的动态分配实例。

请注意,此处最好不要使用newdelete。必要时使用动态内存。当你这样做的时候,你最好使用智能指针,而不是原始指针。

PS您的代码表现出编写getter的糟糕做法,仅仅是因为。PetPerson的所有成员都是公共的。将成员设为私有成员或删除getters。两者兼而有之是误导。此外,如果您确实提供了默认构造函数(Pet() { }(,它应该将成员初始化为有意义的值。构造函数的工作是构造一个处于有效状态的对象。最后但同样重要的是,您应该像在中那样使用构造函数的成员初始值设定项列表

Pet(std::string name, std::string type) : Name(name),Type(type) {}
Pets.push_back(pet); 

创建pet副本并将其存储在向量中。当矢量被销毁时,该副本被销毁(但分配有new的原始fluffy仍然泄漏(。

取消对delete fluffy;的注释时,可以正确地销毁原始对象及其副本。

如果您想跟踪创建的对象的副本,请添加用户定义的副本构造函数和副本赋值运算符(阅读"三条规则"(。

请记住,进入容器的是您指定的对象的副本。在您的情况下,对象*fluffy的副本将进入容器。被销毁的是这个副本(因此调用其析构函数(,而不是在行Pet* fluffy = new Pet("Flyffy", "dog");中创建的原始新对象。因此,使用//delete fluffy;的代码会导致内存泄漏。

如果您考虑将指针本身复制到容器中,请注意,当使用新指针的容器时,请记住在销毁容器之前删除指针(Item 7,Effective STL,Scott Meyers。(,因为销毁指针对象不会调用delete。或者使用@formerlyknownas_463035818 提到的智能指针