繁体   English   中英

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

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

我正在使用一个指向对象的向量。 这些对象派生自基类,并且正在动态分配和存储。

例如,我有类似的东西:

vector<Enemy*> Enemies;

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

enemies.push_back(new Monster());

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

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

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

#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();
}

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

#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();
}

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

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

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

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

通过监督,C ++ 11标准中缺少std::make_unique ,但您可以自己制作一个。 要直接创建unique_ptr (如果可以,不建议使用make_unique ),请执行以下操作:

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

唯一指针只有移动语义; 他们无法复制:

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

这就是我们需要在容器中使用它:

#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_ptr具有引用计数复制语义; 它允许多个所有者共享该对象。 它跟踪一个对象存在多少shared_ptr ,当最后一个不再存在时(该计数变为零),它释放指针。 复制只会增加引用计数(并以较低的,几乎免费的成本移动转移所有权)。 你用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();
}

请记住,您通常希望使用std::unique_ptr作为默认值,因为它更轻量级。 另外, std::shared_ptr可以用std::unique_ptr构建(但反之亦然),所以可以从小开始。

或者,您可以使用创建的容器来存储指向对象的指针,例如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();
}

虽然boost::ptr_vector<T>在C ++ 03中有明显的用途,但我现在不能说相关性,因为我们可以使用std::vector<std::unique_ptr<T>> ,可能几乎没有可比的开销,但应该测试这种说法。

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

作为游戏中的默认值,我可能会使用std::vector<std::shared_ptr<T>> 无论如何,我们希望共享,它足够快,直到分析说不然,它是安全的,并且它易于使用。

我假设如下:

  1. 你有一个像vector <base *>这样的矢量
  2. 在堆上分配对象后,您正在将指针推送到此向量
  3. 你想在这个向量中执行派生*指针的push_back。

以下事情浮现在我的脑海中:

  1. Vector不会释放指针指向的对象的内存。 你必须自己删除它。
  2. 没有什么特定于vector,但基类析构函数应该是虚拟的。
  3. vector <base *>和vector <derived *>是两种完全不同的类型。

使用vector<T*>的麻烦在于,每当向量意外地超出范围时(比如抛出异常时),向量会自行清理,但这只会释放它为保持指针而管理的内存,不是你为指针所指的内存分配的内存。 所以GMan的delete_pointed_to函数价值有限,因为它只在没有出错的情况下才有效。

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

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

(如果你的std lib没有TR1,请使用boost::shared_ptr 。)除了非常罕见的极端情况(循环引用)之外,这简单地消除了对象生存期的麻烦。

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

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

std :: unique()的问题是,向量末尾的[duplicatePosition,end]范围[包含,独占]中的重复项未定义为?。 可能发生的是那些未定义的((?)项可能是额外的重复或缺少重复。

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

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

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

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

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

3)@SEE_ALSO:valgrind(1)。 LINUX上的“内存泄漏”工具在它能找到的东西中是惊人的! 我强烈建议使用它!

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM