在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_ptr
和std::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>>
。 无论如何,我们期望共享,直到性能分析表明,这是足够快的,这是安全的,而且易于使用。
我假设如下:
- 你有一个像vector<base *>的vector
- 分配堆上的对象后,您正在将指针推向此向量
- 你想要派生*指针push_back到这个向量。
以下是我的想法:
- 向量不会释放指针指向的对象的内存。 你必须自己删除它。
- 没有特定的vector,但基类析构函数应该是虚拟的。
- 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无缝工作,而且我在处理两个问题时都遇到了一些问题。 如果我解决不了这些问题,我会发布我的东西,让社区去改进我迄今所做的工作。