智能指针列表 - 管理对象生存期和指针有效性

时间:2012-09-20 22:20:03

标签: c++ list constructor c++11 smart-pointers

我有一个智能指针列表,其中每个指针指向一个单独的Entity类。

std::list<std::unique_ptr<Entity>> m_entities;

我希望构造函数能够处理指向std :: list类的每个指针,因为它是由类实例化上的代码“自动”处理的。但是,如果这个设计不好,那么我会欢迎一个更好的选择,因为它只对我来自C#背景有意义。

Entity::Entity(Game &game) 
    : m_game(game),             
    m_id(m_game.g_idGenerator->generateNewID())             
{
    m_game.m_entities.push_back(std::unique_ptr<Entity>(this));
}

我遇到的这个方法的主要问题是Entity类的生命周期是不受Entity类管理的。

例如,如果我在堆栈上分配一个Entity类,它将在离开分配它的方法后调用Entity析构函数,并且指针将不再有效。

因此,我考虑了创建智能指针的替代方法,将Entity类分配给堆,然后显式地将指针添加到列表中。

std::unique_ptr<Entity> b(new Entity(*this));
m_entities.push_back(b);   // ERROR

这会产生以下错误

error C2664: 'void std::list<_Ty>::push_back(_Ty &&)' : cannot convert parameter 1 from 'std::unique_ptr<_Ty>' to 'std::unique_ptr<_Ty> &&'

什么被认为是将每个指针分配给列表的最佳方法,是否可能是基于构造函数的版本?

我目前认为它是应该处理每个Entity类的生命周期的智能指针列表,并且在构造函数中指定指针不是一个好的设计选择。在这种情况下,我应该创建一个CreateEntity方法,将指针添加到列表而不是让构造函数处理它。这是否更好?

在阅读hereherehere(非现场)的问题后,我考虑了哪种类型的智能指针适合此操作。根据我到目前为止所读到的内容很难得到确切的答案,因为它们都提供了一些相互矛盾的建议。

4 个答案:

答案 0 :(得分:4)

使用构造函数这种方式绝对不是一个好主意,因为构造函数没有关于如何创建和控制对象的信息 - 在堆栈上,静态地,由一些智能指针动态地,通过哑指针动态地?

要解决此问题,您可以使用静态工厂方法创建Entity实例:

class Entity
{
public:

   // Variant with unique ownership
   static void CreateGameEntity(Game& game)
   {
     std::unique_ptr<Entity> p(new Entity());
     game.m_entities.push_back(std::move(p));
   }

   // OR (you cannot use both)

   // Variant with shared ownership
   static std::shared_ptr<Entity> CreateGameEntity(Game& game)
   {
     std::shared_ptr<Entity> p(new Entity());
     game.m_entities.push_back(p);
     return p;
   }

private:

   // Declare ctors private to avoid possibility to create Entity instances
   // without CreateGameEntity() method, e.g. on stack.
   Entity();
   Entity(const Entity&);
};    

使用哪个智能指针?嗯,这取决于你的设计。如果Game对象仅拥有Entity个实例并完全管理其生命周期,则使用std::unique_ptr即可。如果您需要某种共享所有权(例如,您有多个Game个对象可以共享相同的Entity个对象),则应使用std::shared_ptr

如果是唯一所有权,您可以使用Boost Pointer Container library。它包含专门的拥有指针容器,如ptr_vectorptr_listptr_map等。

答案 1 :(得分:2)

我不会评论您的设计问题,但要修复错误,请将代码更改为:

m_entities.push_back(std::unique_ptr<Boundary>(new Boundary(*this, body)));

或:

std::unique_ptr<Boundary> b(new Boundary(*this, body));
m_entities.push_back(std::move(b));

原因是代码中的b是左值,但std::unique_ptr<>是仅移动类型(即没有复制构造函数)。

答案 2 :(得分:2)

代码中的问题是您尝试从l值移动std::unique_ptr<T>std::unique_ptr<T>的实例化是不可复制的,只能移动。要从l值移动,您需要明确地这样做:

this->m_entities.push_back(std::move(b));

std::move()的调用不会真正移动任何东西,但它会产生一种类型,向编译器指示可以移动对象。

答案 3 :(得分:0)

要解决堆栈创建的实例的问题,您可以简单地向构造函数添加一个参数,告诉它不要将新实例添加到列表中,例如:

Entity::Entity(Game &game, bool AddToList = true)  
    : m_game(game),              
      m_id(m_game.g_idGenerator->generateNewID())              
{ 
    if (AddToList) m_game.m_entities.push_back(this); 
} 

{
    ...
    Entity e(game, false);
    ...
}

另一种选择可能是向Entity添加一个析构函数,如果它仍然存在,则将其从列表中删除,但这可能会有点复杂,试图避免直接Entity析构和unique_ptr之间的冲突破坏力。