У меня есть список умных указателей, где каждый указатель указывает на отдельный класс 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 Pointer Container. Он содержит специализированные контейнеры-указатели, такие как ptr_vector
, ptr_list
, ptr_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
в вашем коде есть lvalue, но 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
разрушения и unique_ptr
разрушения.