释放由std::list、vector、map等占用的内存

5

作为一个有C#背景的人,我对于C++的内存管理只有模糊的概念--我只知道我必须手动释放内存。因此,我的C++代码是这样编写的:类型为std::vectorstd::liststd::map的对象被随意实例化、使用,但没有被释放。

直到我完成程序之前,我才意识到这一点,现在我的代码由以下几种模式组成:

struct Point_2
{
    double x;
    double y;
};

struct Point_3
{
    double x;
    double y;
    double z;
};

list<list<Point_2>> Computation::ComputationJob
    (list<Point_3>pts3D, vector<Point_2>vectors)
{
    map<Point_2, double> pt2DMap=ConstructPointMap(pts3D);
    vector<Point_2> vectorList = ConstructVectors(vectors);
    list<list<Point_2>> faceList2D=ConstructPoints(vectorList , pt2DMap);
    return faceList2D;
}

我的问题是,我是否必须释放列表使用的每一个单独的元素(在上面的示例中,这意味着我必须释放pt2DMapvectorListfaceList2D)?那将非常繁琐!我可能会重写我的Computation类,使其更不容易出现内存泄漏。有什么好的解决方法吗?

2
实际上,在方法参数中使用引用更好。复制是不高效的。 - ssmir
@ssmir,C#中的老习惯难改! - Graviton
3个回答

11
不需要使用new分配对象时,无需显式释放/删除。当它们超出范围时,它们会自动被释放。此时将调用析构函数,应该释放它们引用的所有对象。(这称为资源获取即初始化(RAII),标准类如std::liststd::vector遵循此模式。)
如果您使用new,则应使用智能指针(scoped_ptr)或显式调用delete。最好在析构函数中调用delete(出于异常安全的原因),但尽可能使用智能指针。

@larsmans,你的意思是说我不需要处理std::list的内存释放问题吗? - Graviton
由于所有的复制,当与C++容器结合使用时,简单的RAII可能会有些灾难性的情况。 - T.E.D.
1
@Graviton - 你不需要这样做。但是,你必须处理map可能会复制给它的对象这一事实。如果你的对象的默认构造函数和析构函数很复杂,那么这可能对你来说是一个非常重要的问题。 - T.E.D.
@Graviton 重申答案中最重要的部分。如果你调用了new,就必须调用delete。唯一需要关注显式清理内存的时间是在显式分配内存时。还有一件事需要注意,即释放相同内存两次会导致错误。 - stonemetal
1
如果你使用了new,那么应当显式地调用delete。最好的方法是在析构函数中这样做(出于异常安全的考虑)。我强烈反对这种做法。像boost::scoped_ptr这样的智能指针会更好,它避免了使用裸指针时可能会出现的各种问题,比如stonemetal提到的重复释放同一个对象的问题。 - etarion
显示剩余2条评论

2
总的来说,C++标准容器在幕后会复制您的对象。这是您无法控制的。这意味着,如果您的对象构造(例如:在您的情况下是Point_2)涉及任何资源分配(例如:newmalloc调用),则必须编写自定义版本的复制构造函数和析构函数,以使其在地图决定复制Point_2时表现得合理。通常,这涉及引用计数等技术。
许多人发现,将指向复杂对象的指针放入标准容器中比放置对象本身更容易。
如果您在构造函数或析构函数中没有做任何特殊处理(现在似乎是这种情况),则没有任何问题。一些容器(如映射)将在幕后进行动态分配,但这对您来说实际上是不可见的。容器会关注它们的资源分配。您只需要关注您的资源分配。

1
问题已更新。我不太确定你的意思,因为在上面的代码中,我不需要使用newmalloc来为Point_2Point_3分配内存,所以我猜这意味着我不需要任何自定义代码来进行内存管理。 - Graviton
同意。对于那些(通常被称为POD或Plain Old Data类型),您可以放心地使用STL容器,而不必担心动态分配。 - T.E.D.

0

所有STL容器都会自动清除它们的内容,您只需关心动态分配的数据的清理(即规则是:注意指针)。

例如,如果您有list<MyType>,列表中包含某些自定义类型的对象,当销毁它时,它将调用~MyType(),该函数应负责正确清除对象内容(即,如果MyType内部具有一些指向分配内存的指针,则应在析构函数中删除它们)。

另一方面,如果您开始使用list<MyType*>,则容器不知道如何正确清理它,它包含一些标量值(就像整数),并且仅删除指针本身,而不清除指向的内容,因此您需要手动清理。

当从Java/C#切换到C++时,一个真正好的建议(多年前对我很有帮助:))是仔细跟踪每个动态内存对象的生命周期:a)它在哪里被创建,b)它在哪里使用,c)在何时何地删除它。确保它只被清理一次,之后不再引用!


2
将“std”更改为“stl”,因为STD在英语中有一些不幸的内涵。如果您愿意,可以恢复。我确实同意,如果所有std容器都可以自动清理其内容,那么世界将会变得更美好。 :-) - T.E.D.
低估的评论被低估了 - EA304GT

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接