在 C++ 中使用指向动态分配对象的指针向量时如何避免内存泄漏?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1361139/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-27 19:41:50  来源:igfitidea点击:

How to avoid memory leaks when using a vector of pointers to dynamically allocated objects in C++?

c++stlpointersvectorderived

提问by akif

I'm using a vector of pointers to objects. These objects are derived from a base class, and are being dynamically allocated and stored.

我正在使用指向对象的指针向量。这些对象是从基类派生的,并被动态分配和存储。

For example, I have something like:

例如,我有类似的东西:

vector<Enemy*> Enemies;

and I'll be deriving from the Enemy class and then dynamically allocating memory for the derived class, like this:

我将从 Enemy 类派生,然后为派生类动态分配内存,如下所示:

enemies.push_back(new Monster());

What are things I need to be aware of to avoid memory leaks and other problems?

为了避免内存泄漏和其他问题,我需要注意哪些事项?

回答by GManNickG

std::vectorwill manage the memory for you, like always, but this memory will be of pointers, not objects.

std::vector将像往常一样为您管理内存,但此内存将是指针,而不是对象。

What this means is that your classes will be lost in memory once your vector goes out of scope. For example:

这意味着一旦您的向量超出范围,您的类将在内存中丢失。例如:

#include <vector>

struct base
{
    virtual ~base() {}
};

struct derived : base {};

typedef std::vector<base*> container;

void foo()
{
    container c;

    for (unsigned i = 0; i < 100; ++i)
        c.push_back(new derived());

} // leaks here! frees the pointers, doesn't delete them (nor should it)

int main()
{
    foo();
}

What you'd need to do is make sure you delete all the objects before the vector goes out of scope:

您需要做的是确保在向量超出范围之前删除所有对象:

#include <algorithm>
#include <vector>

struct base
{
    virtual ~base() {}
};

struct derived : base {};

typedef std::vector<base*> container;

template <typename T>
void delete_pointed_to(T* const ptr)
{
    delete ptr;
}

void foo()
{
    container c;

    for (unsigned i = 0; i < 100; ++i)
        c.push_back(new derived());

    // free memory
    std::for_each(c.begin(), c.end(), delete_pointed_to<base>);
}

int main()
{
    foo();
}

This is difficult to maintain, though, because we have to remember to perform some action. More importantly, if an exception were to occur in-between the allocation of elements and the deallocation loop, the deallocation loop would never run and you're stuck with the memory leak anyway! This is called exception safety and it's a critical reason why deallocation needs to be done automatically.

但是,这很难维护,因为我们必须记住执行某些操作。更重要的是,如果在元素分配和释放循环之间发生异常,释放循环将永远不会运行,无论如何你都会被内存泄漏所困扰!这称为异常安全,这是需要自动完成释放的一个关键原因。

Better would be if the pointers deleted themselves. Theses are called smart pointers, and the standard library provides std::unique_ptrand std::shared_ptr.

如果指针删除自己会更好。这些被称为智能指针,标准库提供std::unique_ptrstd::shared_ptr

std::unique_ptrrepresents a unique (unshared, single-owner) pointer to some resource. This should be your default smart pointer, and overall complete replacement of any raw pointer use.

std::unique_ptr表示指向某个资源的唯一(非共享、单一所有者)指针。这应该是您的默认智能指针,并且完全替代了任何原始指针的使用。

auto myresource = /*std::*/make_unique<derived>(); // won't leak, frees itself

std::make_uniqueis missing from the C++11 standard by oversight, but you can make one yourself. To directly create a unique_ptr(not recommended over make_uniqueif you can), do this:

std::make_unique由于疏忽,C++11 标准中缺少它,但您可以自己制作一个。要直接创建一个unique_ptrmake_unique如果可以,不推荐),请执行以下操作:

std::unique_ptr<derived> myresource(new derived());

Unique pointers have move semantics only; they cannot be copied:

唯一指针只有移动语义;它们不能被复制:

auto x = myresource; // error, cannot copy
auto y = std::move(myresource); // okay, now myresource is empty

And this is all we need to use it in a container:

这就是我们在容器中使用它所需的全部内容:

#include <memory>
#include <vector>

struct base
{
    virtual ~base() {}
};

struct derived : base {};

typedef std::vector<std::unique_ptr<base>> container;

void foo()
{
    container c;

    for (unsigned i = 0; i < 100; ++i)
        c.push_back(make_unique<derived>());

} // all automatically freed here

int main()
{
    foo();
}

shared_ptrhas reference-counting copy semantics; it allows multiple owners sharing the object. It tracks how many shared_ptrs exist for an object, and when the last one ceases to exist (that count goes to zero), it frees the pointer. Copying simply increases the reference count (and moving transfers ownership at a lower, almost free cost). You make them with std::make_shared(or directly as shown above, but because shared_ptrhas to internally make allocations, it's generally more efficient and technically more exception-safe to use make_shared).

shared_ptr具有引用计数复制语义;它允许多个所有者共享对象。它跟踪shared_ptr一个对象存在多少个s,当最后一个不再存在时(该计数变为零),它释放指针。复制只是增加了引用计数(并且以更低的、几乎免费的成本转移所有权)。您可以使用std::make_shared(或直接如上所示,但由于shared_ptr必须在内部进行分配,因此使用它通常更有效且技术上更安全make_shared)。

#include <memory>
#include <vector>

struct base
{
    virtual ~base() {}
};

struct derived : base {};

typedef std::vector<std::shared_ptr<base>> container;

void foo()
{
    container c;

    for (unsigned i = 0; i < 100; ++i)
        c.push_back(std::make_shared<derived>());

} // all automatically freed here

int main()
{
    foo();
}

Remember, you generally want to use std::unique_ptras a default because it's more lightweight. Additionally, std::shared_ptrcan be constructed out of a std::unique_ptr(but not vice versa), so it's okay to start small.

请记住,您通常希望将其std::unique_ptr用作默认值,因为它更轻量级。此外,std::shared_ptr可以用 a 构建std::unique_ptr(但反之不行),所以从小处开始是可以的。

Alternatively, you could use a container created to store pointers to objects, such as a boost::ptr_container:

或者,您可以使用创建的容器来存储指向对象的指针,例如boost::ptr_container

#include <boost/ptr_container/ptr_vector.hpp>

struct base
{
    virtual ~base() {}
};

struct derived : base {};

// hold pointers, specially
typedef boost::ptr_vector<base> container;

void foo()
{
    container c;

    for (int i = 0; i < 100; ++i)
        c.push_back(new Derived());

} // all automatically freed here

int main()
{
    foo();
}

While boost::ptr_vector<T>had obvious use in C++03, I can't speak of the relevance now because we can use std::vector<std::unique_ptr<T>>with probably little to no comparable overhead, but this claim should be tested.

虽然boost::ptr_vector<T>在 C++03 中有明显的用途,但我现在不能谈论相关性,因为我们可以使用std::vector<std::unique_ptr<T>>很少甚至没有可比的开销,但是这个声明应该被测试。

Regardless, never explicitly free things in your code. Wrap things up to make sure resource management is dealt with automatically. You should have no raw owning pointers in your code.

无论如何,永远不要在您的代码中明确释放内容。总结一下以确保自动处理资源管理。你的代码中不应该有原始的拥有指针。

As a default in a game, I would probably go with std::vector<std::shared_ptr<T>>. We expect sharing anyway, it's fast enough until profiling says otherwise, it's safe, and it's easy to use.

作为游戏的默认设置,我可能会选择std::vector<std::shared_ptr<T>>. 无论如何,我们希望共享,它足够快,直到分析表明否则,它是安全的,并且易于使用。

回答by sbi

The trouble with using vector<T*>is that, whenever the vector goes out of scope unexpectedly (like when an exception is thrown), the vector cleans up after yourself, but this will only free the memory it manages for holding the pointer, not the memory you allocated for what the pointers are referring to. So GMan's delete_pointed_tofunctionis of limited value, as it only works when nothing goes wrong.

使用的麻烦vector<T*>在于,每当 vector 意外超出范围时(例如抛出异常时),vector 会自行清理,但这只会释放它为保存指针而管理的内存,而不是您分配的内存对于指针所指的内容。所以GMan 的delete_pointed_to功能价值有限,因为它只有在没有出错的情况下才起作用。

What you need to do is to use a smart pointer:

您需要做的是使用智能指针:

vector< std::tr1::shared_ptr<Enemy> > Enemies;

(If your std lib comes without TR1, use boost::shared_ptrinstead.) Except for very rare corner cases (circular references) this simply removes the trouble of object lifetime.

(如果您的标准库没有 TR1,请boost::shared_ptr改用。)除了非常罕见的极端情况(循环引用),这只是消除了对象生命周期的麻烦。

Edit: Note that GMan, in his detailed answer, mentions this, too.

编辑:请注意,GMan 在他的详细回答中也提到了这一点。

回答by Naveen

I am assuming following:

我假设如下:

  1. You are having a vector like vector< base* >
  2. You are pushing the pointers to this vector after allocating the objects on heap
  3. You want to do a push_back of derived* pointer into this vector.
  1. 你有一个像 vector<base*> 这样的向量
  2. 在堆上分配对象后,您正在将指针推送到此向量
  3. 您想要将派生 * 指针的 push_back 推入该向量。

Following things come to my mind:

我想到了以下几点:

  1. Vector will not release the memory of the object pointed to by the pointer. You have to delete it itself.
  2. Nothing specific to vector, but the base class destructor should be virtual.
  3. vector< base* > and vector< derived* > are two totally different types.
  1. Vector 不会释放指针指向的对象的内存。你必须自己删除它。
  2. 没有特定于向量,但基类析构函数应该是虚拟的。
  3. vector<base*> 和 vector<derived*> 是两种完全不同的类型。

回答by dennis bednar

One thing to be very careful is IF there are two Monster() DERIVED objects whose contents are identical in value. Suppose that you wanted to remove the DUPLICATE Monster objects from your vector (BASE class pointers to DERIVED Monster objects). If you used the standard idiom for removing duplicates (sort, unique, erase: see LINK #2], you will run into memory leak issues, and/or duplicate delete problems, possibly leading to SEGMENTATION VOIOLATIONS (I have personally seen these problems on LINUX machine).

需要非常小心的一件事是,如果有两个 Monster() DERIVED 对象的内容值相同。假设您想从您的向量中删除 DUPLICATE Monster 对象(指向 DERIVED Monster 对象的基类指针)。如果您使用标准习语来删除重复项(排序、唯一、擦除:请参阅链接 #2],您将遇到内存泄漏问题和/或重复删除问题,可能导致 SEGMENTATION VOIOLATIONS(我个人在LINUX 机器)。

THe problem with the std::unique() is that the duplicates in the [duplicatePosition,end) range [inclusive, exclusive) at the end of the vector are undefined as ?. What can happen is that those undefined ((?) items might be extra duplicate or a missing duplicate.

std::unique() 的问题在于向量末尾的 [duplicatePosition,end) 范围 [inclusive,exclusive) 中的重复项未定义为 ?。可能发生的情况是那些未定义的 ((?) 项目可能是额外的重复项或丢失的重复项。

The problem is that std::unique() isn't geared to handle a vector of pointers properly. The reason is that std::unique copies uniques from the end of the vector "down" toward the beginning of the vector. For a vector of plain objects this invokes the COPY CTOR, and if the COPY CTOR is written properly, there is no problem of memory leaks. But when its a vector of pointers, there is no COPY CTOR other than "bitwise copy", and so the pointer itself is simply copied.

问题是 std::unique() 不适合正确处理指针向量。原因是 std::unique 从向量的末尾“向下”向向量的开头复制唯一值。对于普通对象的向量,这会调用 COPY CTOR,如果 COPY CTOR 写入正确,则不存在内存泄漏问题。但是当它是一个指针向量时,除了“按位复制”之外没有 COPY CTOR,因此指针本身被简单地复制。

THere are ways to solve these memory leak other than using a smart pointer. One way to write your own slightly modified version of std::unique() as "your_company::unique()". The basic trick is that instead of copying an element, you would swap two elements. And you would have to be sure that the instead of comparing two pointers, you call BinaryPredicate that follows the two pointers to the object themselves, and compare the contents of those two "Monster" derived objects.

除了使用智能指针之外,还有其他方法可以解决这些内存泄漏问题。将您自己的 std::unique() 稍微修改版本编写为“your_company::unique()”的一种方法。基本技巧是,不是复制一个元素,而是交换两个元素。并且您必须确保不是比较两个指针,而是调用跟随指向对象本身的两个指针的 BinaryPredicate,并比较这两个“Monster”派生对象的内容。

1) @SEE_ALSO: http://www.cplusplus.com/reference/algorithm/unique/

1)@SEE_ALSO:http://www.cplusplus.com/reference/algorithm/unique/

2) @SEE_ALSO: What's the most efficient way to erase duplicates and sort a vector?

2)@SEE_ALSO: 删除重复项和对向量进行排序的最有效方法是什么?

2nd link is excellently written, and will work for a std::vector but has memory leaks, duplicate frees (sometimes resulting in SEGMENTATION violations) for a std::vector

第二个链接写得非常好,适用于 std::vector 但有内存泄漏、重复释放(有时会导致 SEGMENTATION 违规)用于 std::vector

3) @SEE_ALSO: valgrind(1). THis "memory leak" tool on LINUX is amazing in what it can find! I HIGHLY recommend using it!

3) @SEE_ALSO: valgrind(1)。这个 LINUX 上的“内存泄漏”工具在它可以找到的东西方面是惊人的!我强烈推荐使用它!

I hope to post a nice version of "my_company::unique()" in a future post. Right now, its not perfect, because I want the 3-arg version having BinaryPredicate to work seamlessly for either a function pointer or a FUNCTOR, and I'm having some problems handling both properly. IF I cannot solve those problems, I'll post what I have, and let the community have a go at improving on what I have done so far.

我希望在以后的帖子中发布一个不错的“my_company::unique()”版本。现在,它并不完美,因为我希望具有 BinaryPredicate 的 3-arg 版本能够为函数指针或 FUNCTOR 无缝工作,而且我在正确处理两者时遇到了一些问题。如果我无法解决这些问题,我会发布我所拥有的,并让社区尝试改进我目前所做的。