aki*_*kif 66 c++ pointers stl vector derived
我正在使用一个指向对象的向量.这些对象派生自基类,并且正在动态分配和存储.
例如,我有类似的东西:
vector<Enemy*> Enemies;
Run Code Online (Sandbox Code Playgroud)
我将从Enemy类派生,然后为派生类动态分配内存,如下所示:
enemies.push_back(new Monster());
Run Code Online (Sandbox Code Playgroud)
为了避免内存泄漏和其他问题,我需要注意哪些事项?
GMa*_*ckG 145
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();
}
Run Code Online (Sandbox Code Playgroud)
您需要做的是确保在向量超出范围之前删除所有对象:
#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();
}
Run Code Online (Sandbox Code Playgroud)
但这很难维护,因为我们必须记住执行某些操作.更重要的是,如果在元素分配和释放循环之间发生异常,则释放循环将永远不会运行,并且您仍然会遇到内存泄漏!这称为异常安全,这是解除分配需要自动完成的关键原因.
如果指针删除自己会更好.这些被称为智能指针,标准库提供std::unique_ptr和std::shared_ptr.
std::unique_ptr表示指向某个资源的唯一(非共享,单个所有者)指针.这应该是您的默认智能指针,并且整体完全替换任何原始指针使用.
auto myresource = /*std::*/make_unique<derived>(); // won't leak, frees itself
Run Code Online (Sandbox Code Playgroud)
std::make_unique由于监督而缺少C++ 11标准,但您可以自己制作一个.要直接创建unique_ptr(make_unique如果可以的话,不推荐使用),请执行以下操作:
std::unique_ptr<derived> myresource(new derived());
Run Code Online (Sandbox Code Playgroud)
唯一指针只有移动语义; 他们无法复制:
auto x = myresource; // error, cannot copy
auto y = std::move(myresource); // okay, now myresource is empty
Run Code Online (Sandbox Code Playgroud)
这就是我们需要在容器中使用它:
#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();
}
Run Code Online (Sandbox Code Playgroud)
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();
}
Run Code Online (Sandbox Code Playgroud)
请记住,您通常希望将其std::unique_ptr用作默认值,因为它更轻量级.另外,std::shared_ptr可以用a构建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();
}
Run Code Online (Sandbox Code Playgroud)
虽然boost::ptr_vector<T>在C++ 03中有明显的用途,但我现在不能谈论相关性,因为我们可以使用std::vector<std::unique_ptr<T>>很少甚至没有可比较的开销,但是这个声明应该被测试.
无论如何,永远不要在代码中明确地释放内容.总结一下以确保自动处理资源管理.您的代码中应该没有原始的拥有指针.
作为游戏的默认设置,我可能会选择std::vector<std::shared_ptr<T>>.无论如何,我们希望共享,它足够快,直到分析说不然,它是安全的,并且它易于使用.
我假设如下:
以下事情浮现在我的脑海中:
使用的麻烦vector<T*>在于,每当向量意外地超出范围时(比如抛出异常),向量会自行清理,但这只会释放它为保存指针而管理的内存,而不是你分配的内存指针指的是什么.因此,GMan的delete_pointed_to功能价值有限,因为它只在没有出错的情况下才有效.
你需要做的是使用智能指针:
vector< std::tr1::shared_ptr<Enemy> > Enemies;
Run Code Online (Sandbox Code Playgroud)
(如果你的std lib没有TR1,请使用boost::shared_ptr.)除了非常罕见的极端情况(循环引用)之外,这简单地消除了对象生存期的麻烦.
编辑:请注意,GMan在他的详细答案中也提到了这一点.