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

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

例如,我有这样的东西:

vector<Enemy*> Enemies; 

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

 enemies.push_back(new Monster()); 

什么是我需要注意的事情,以避免内存泄漏和其他问题?

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

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

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

但这很难维持,因为我们必须记住要采取一些行动。 更重要的是,如果在元素分配和释放循环之间发生exception,那么释放循环将永远不会运行,并且无论如何都会造成内存泄漏! 这被称为exception安全,这是自动重新分配需要自动完成的一个重要原因。

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

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

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

从C ++ 11标准中忽略了std::make_unique ,但是你可以自己创build一个。 要直接创build一个unique_ptr (如果可以,不build议通过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具有引用计数复制语义; 它允许多个所有者共享该对象。 它追踪一个对象有多less个shared_ptr存在,当最后一个不存在(数到零)时,它释放指针。 复制只需增加参考计数(并以较低的几乎免费的成本移动转移所有权)。 你使用std::make_shared (或者直接如上所示),但是因为shared_ptr必须在内部进行分配,所以使用make_shared通常更高效,技术上更加exception安全。

 #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 (但反之亦然),所以可以从小开始。

或者,您可以使用创build的容器来存储指向对象的指针,例如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>> ,可能几乎没有可比的开销,但是这个声明应该被testing。

无论如何, 从来没有明确地将代码中的东西释放 。 把东西包起来,确保资源pipe理是自动处理的。 您的代码中应该没有原始的拥有指针。

作为一个游戏的默认,我可能会用std::vector<std::shared_ptr<T>> 。 无论如何,我们期望共享,直到性能分析表明,这是足够快的,这是安全的,而且易于使用。

我假设如下:

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

以下是我的想法:

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

使用vector<T*>的麻烦在于,只要向量超出范围意外(例如抛出exception),向量就会自行清理,但这只会释放它为pipe理指针所pipe理的内存,而不是你分配的指针指向的内存。 所以GMan的delete_pointed_to函数是有限的,因为它只有在没有任何错误的情况下才起作用。

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

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

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

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

有一件事要非常小心,如果有两个Monster()DERIVED对象的内容是相同的值。 假设你想从你的vector中移除DUPLICATE Monster对象(BASE类指针指向DERIVED Monster对象)。 如果你使用标准成语删除重复(sorting,唯一,擦除:请参阅链接#2),你会遇到内存泄漏问题,和/或重复删除问题,可能导致分段VOIOLATIONS(我亲眼看到这些问题LINUX机器)。

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

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

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

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

2)@SEE_ALSO: 什么是最有效的方式来删除重复和sorting向量?

第二个链接是出色的写入,并将为一个std :: vector工作,但有内存泄漏,重复释放(有时导致分段违规)一个std ::向量

3)@SEE_ALSO:valgrind(1)。 这个LINUX上的“内存泄漏”工具在它的function上是惊人的! 我强烈build议使用它!

我希望在以后的文章中发布一个很好的“my_company :: unique()”版本。 现在,它并不完美,因为我希望BinaryPredicate的3-arg版本可以为函数指针或FUNCTOR无缝工作,而且我在处理两个问题时都遇到了一些问题。 如果我解决不了这些问题,我会发布我的东西,让社区去改进我迄今所做的工作。