我有一个智能指针列表,其中每个指针指向一个单独的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方法,将指针添加到列表而不是让构造函数处理它。这是否更好?
在阅读here,here和here(非现场)的问题后,我考虑了哪种类型的智能指针适合此操作。根据我到目前为止所读到的内容很难得到确切的答案,因为它们都提供了一些相互矛盾的建议。
答案 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_vector
,ptr_list
,ptr_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
之间的冲突破坏力。