Список интеллектуальных указателей — Управление временем жизни объекта и сроком действия указателя

У меня есть список умных указателей, где каждый указатель указывает на отдельный класс 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, который добавляет указатель в список, а не позволяет конструктору обрабатывать его. Это лучше?

Я подумал, какой тип умного указателя будет уместен для этой операции после прочтения найденных вопросов Вот, Вот а также Вот (Выездные). Трудно получить точный ответ, основываясь на том, что я прочитал, хотя они все предлагают несколько противоречивые советы.

2

Решение

Использование конструктора таким способом определенно не является хорошей идеей, потому что конструктор не имеет информации о том, как объект создается и управляется — в стеке, статически, динамически с помощью некоторого умного указателя, динамически с помощью тупого указателя?

Чтобы решить эту проблему, вы можете использовать статический метод фабрики для создания 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 и т.п.

4

Другие решения

Я не буду комментировать ваши вопросы о дизайне, но чтобы исправить ошибку, измените код на:

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<> является типом только для перемещения (т.е. не имеет конструктора копирования).

2

Проблема в вашем коде в том, что вы пытаетесь переместить std::unique_ptr<T> из l-значения. Экземпляры std::unique_ptr<T> не подлежат копированию и являются только подвижными. Чтобы перейти от значения l, вам нужно сделать это явно:

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

Призыв к std::move() на самом деле ничего не перемещает, но дает тип, который указывает компилятору, что объект может быть перемещен.

2

Чтобы решить проблему с экземпляром, созданным в стеке, вы можете просто добавить параметр в конструктор, который скажет ему не добавлять новый экземпляр в список, например:

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 разрушения.

0
По вопросам рекламы [email protected]