STL 容器和内存管理 - 对象列表与指向对象的指针列表

STL Containers & Memory Management - list of objects vs. list of pointers to objects

本文关键字:对象 列表 指针 内存 管理 STL      更新时间:2023-10-16

我已经很好地了解了关于这个主题的其他一些问题,其中没有一个(据我所知)解决了如何从包含动态分配内存的stl对象列表中正确擦除项目与不包含动态分配内存的stl对象列表。

我想使用对象列表。以这个对象为例(它不包含动态分配的内存):

class MyPoint {
public:
    MyPoint(int _x,int _y)
    {
        x = _x;
        y = _y;
    }
private:
    int x;
    int y;
};

所以我可能会创建一个对象列表(不是指向它们的指针),向其中添加内容,然后删除元素:

list<MyPoint> myList;
myList.push_back(MyPoint(3,4));
myList.push_back(MyPoint(1,2));
myList.push_back(MyPoint(8,8));
myList.push_back(MyPoint(-1,2));
list<MyPoint>::iterator it;
it = myList.begin();
advance(it,2);
myList.erase(it);

我的列表现在包含:(3、4)(1、2)(1、2)

  • 问题1a:我需要对被擦除的对象做任何其他事情吗?还是内存会被处理?

  • 问题1b:如果程序结束,我需要对列表中剩余的对象做些什么吗?我需要把它们全部删除,然后处理它们的记忆吗?

好,现在考虑这个类的另一个版本,它允许在n维空间中有一个点。也就是说,我可以动态地分配一个长度为N的数组来保存类中的N个点(我已经省去了实现,因为这里没有问题)。然后,类的析构函数将使用'delete'删除动态赋值的数组。

class MyDynamicPoint {
public:
    MyDynamicPoint(int N)
    {
        points = new int[N];
    }
    ~MyDynamicPoint()
    {
        delete points;
        points = NULL;
    }
private:
    int *points;
};

我现在可以创建一个指向对象的指针列表,而不是对象本身:

list<MyDynamicPoint*> myList;
myList.push_back(new MyDynamicPoint(8));
myList.push_back(new MyDynamicPoint(10));
myList.push_back(new MyDynamicPoint(2));
myList.push_back(new MyDynamicPoint(50));
list<MyDynamicPoint*>::iterator it;
it = myList.begin();
advance(it,2);
myList.erase(it);
  • 问题2a -以上内容正确吗?也就是说,因为这个新版本的类将包含一些动态分配的内存,这是否意味着我必须创建一个指向对象的指针列表,而不是对象本身?

  • 问题2b -鉴于我刚刚从列表中删除了指针,我在哪里调用delete来处理现在在对象中有动态内存要删除的事实?还是stl list的erase方法调用对象的析构函数来处理它?

提前感谢您的帮助,

,

Adam

当你有一个类的数据成员具有自动存储持续时间(即它们的生命周期与这个类的实例绑定),像这样:

class MyPoint {
private:
    int x;
    int y;
};

,您将使用list<MyPoint> myList;,那么std::list的这个实例也是一个具有自动存储持续时间的对象,它将被自动清除,并且在容器被销毁时,它所保存的元素也将被清除。一切都安排妥当

但是后一个版本不是很幸运的选择…您不仅有一个保存指针的容器,您甚至决定创建一个将动态分配的类Point的数据成员。首先注意,通过调用new分配的所有内容应该通过调用delete来释放,而通过调用new[]分配的所有内容应该通过调用delete[]来释放。

在这种情况下,您在构造对象时分配内存,并在对象销毁时清理内存:

MyDynamicPoint(int N)
{
    points = new int[N];
}
~MyDynamicPoint()
{
    delete[] points;
    points = NULL;
}
private:
int *points;

您可以通过使用一些std::vectorstd::array而不是c风格的数组来实现相同的目标,并且您不必自己负责内存管理:

MyDynamicPoint(int N) : points(std::vector<int>(N, 0)) { }
private:
std::vector<int> points;

std::vector对象将为您负责内存管理。

最后一件事:当你动态分配一个元素并将其存储到容器中时:

myList.push_back(new MyDynamicPoint(8));

你需要自己释放这个内存,从列表中擦除指针是不够的:

list<MyDynamicPoint*>::iterator it;
...
delete *it;
myList.erase(it);
因此,无论您想要实现什么,如果情况允许,总是首选具有自动存储持续时间的对象。没有什么比被迫手动处理内存管理和处理令人不快的问题(如内存泄漏)更糟糕的了。

问题1a:我需要对被擦除的对象做任何其他事情吗?还是内存会被处理?

你不需要做任何事。

问题1b:如果程序结束,我需要对列表中剩余的对象做些什么吗?我需要把它们全部删除,然后处理它们的记忆吗?

你不需要做任何事。

问题2a -以上内容正确吗?

代码不正确。你违反了"三原则"。特别是,自动生成的MyDynamicPoint的复制构造函数和赋值操作符将对points指针进行逐位复制。如果复制MyDynamicPoint的实例,最终会得到两个对象共享同一个points指针:

  • 当一个对象超出作用域时,另一个对象变得不可用。
  • 当第二个对象超出作用域时,它的析构函数将尝试释放已经释放的内存。这是未定义行为。

。因为这个新版本的类将包含一些动态分配的内存,这是否意味着我必须创建一个指向对象的指针列表,而不是对象本身?

不,这不是那个意思。实际上,您可能应该继续按值存储对象。但是,您确实需要修改"三"规则。

问题2b -假设我刚刚从列表中删除了指针,我在哪里调用delete来处理对象中现在有动态内存要删除的事实?还是stl list的erase方法调用对象的析构函数来处理它?

由于您有一个原始指针列表,因此不会自动调用析构函数。解决这个问题的最简单方法是按值存储对象,或者使用std::unique_ptrstd::shared_ptr代替原始指针。

对于问题1,您不需要做任何事情。当您按值存储对象时,编译器和库将处理一切。

然而,当你像第二种情况一样存储指针时,你需要delete那些你用new分配的指针,否则你会有内存泄漏。

必须在擦除之前删除指针,因为这会使迭代器失效:

delete *it;
myList.erase(it);

我认为以下应该可以工作

MyPoint* ptr = myList.back();
delete ptr;
myList.pop_back();

MyPoint* ptr = myList.back();
delete ptr;
myList.erase(ptr);