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

List of smart pointers - Managing object lifetime and pointer validity

本文关键字:指针 生存期 有效性 对象 管理 列表 智能      更新时间:2023-10-16

我有一个智能指针列表,其中每个指针指向一个单独的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方法,将指针添加到列表,而不是让构造函数处理它。这样更好吗?

在阅读了这里、这里和这里(场外)的问题后,我考虑了什么类型的智能指针适合此操作。根据我目前所读到的内容,很难得到确切的答案,尽管它们都提供了一些相互矛盾的建议。

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

为了解决这个问题,您可以使用静态工厂方法来创建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指针容器库。它包含专门拥有的指针容器,如ptr_vectorptr_listptr_map等。

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

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<>是一个仅移动类型(即没有复制构造函数)。

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

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

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

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

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破坏之间的冲突,这可能会变得有点复杂。