Работа с unique_ptr в контейнерах

У меня есть вектор unique_ptrs, который указывает на модели, сетки и т. д., например:

std::vector<std::unique_ptr<Model>> mLoadedModels;

Я выбираю unique_ptr, потому что он автоматически освобождает данные при деструкторе вектора, а также потому, что позже, если мне нужно, например, перезагрузить все модели (из-за разрыва/создания контекста OpenGL), я могу просто внутренне в моем диспетчере ресурсов reset() и заставьте его указывать на новый экземпляр модели, и это не повлияет на остальную часть системы.

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

Решение, которое я придумал, заключается в следующем, чтобы обернуть доступ в следующую структуру:

template<typename T>
struct Handle
{
    Handle(std::unique_ptr<T>& resource) : mResource(resource)
    {
    }

    T& operator*()                  { return mResource.get(); }
    const T& operator*() const      { return mResource.get(); }
    T* operator->()                 { return mResource.get(); }
    const T* operator->() const     { return mResource.get(); }


private:
    std::unique_ptr<T>& mResource;
};

typedef Handle<Model> ModelPtr;

ModelPtr GetModel(const std::string& modelName);

// example:
ModelPtr monkey = GetModel("Monkey");
monkey->dance();

// reload resources, and then monkey dereferences to the new Model instance 

Это кажется немного бесполезным, конечно, есть лучшее, более простое решение для этого?


person KaiserJohaan    schedule 19.07.2013    source источник
comment
Используйте shared_ptr, если вам нужно совместное владение. Затем вы можете раздать weak_ptrs наблюдателям.   -  person Praetorian    schedule 19.07.2013
comment
Handle, очевидно, следует назвать unique_ptr_ptr. Шутки в сторону, ваш Handle будет аннулирован при изменении размера вектора.   -  person Casey    schedule 19.07.2013
comment
Что плохого в раздаче необработанных Model*?   -  person Igor Tandetnik    schedule 19.07.2013
comment
@Casey действительно, это должно быть shared_unique_ptr для решения жизненных проблем (я шучу).   -  person Yakk - Adam Nevraumont    schedule 19.07.2013
comment
@Yakk Мне нравится использовать shared_unique_ptr*, чтобы охватить все мои базы.   -  person Casey    schedule 19.07.2013
comment
@Casey, но что, если вы хотите переустановить его? shared_unique_ptr*& чтобы быть в безопасности.   -  person Yakk - Adam Nevraumont    schedule 19.07.2013
comment
@Yakk Но вы не можете передать это std::bind. reference_wrapper<shared_unique_ptr*> гораздо более гибкий.   -  person Casey    schedule 19.07.2013


Ответы (2)


Для этого есть простое решение.

Обходите vec[n].get() -- необработанные указатели. Пока вы не храните их и всегда получаете обратно от владельца, а владелец не уничтожает их, пока вы их используете, вы в безопасности.

Если вы не желаете следовать такому уровню дисциплины, вам нужно std::shared_ptr в vector, а также передавать и хранить std::weak_ptr. weak_ptr будут автоматически аннулированы, когда последний shared_ptr уйдет (и согласно политике единственным постоянным shared_ptr является тот, который принадлежит vector).

Это имеет дополнительное преимущество: если вы выполняете работу над элементом, а vector очищается, вы не ошибаетесь. Вы обращаетесь к weak_ptr с помощью .lock(), который возвращает shared_ptr, в течение жизни которого исходный указатель гарантированно будет хорошим.

Недостатком является то, что это увеличивает стоимость, а преимуществом является то, что это допускает слабое совместное владение и ленивое уведомление об аннулировании.

person Yakk - Adam Nevraumont    schedule 19.07.2013
comment
Однако мне нужно их хранить, например, моя модель хранит MeshPtr и MaterialPtr и т. д. Я также хочу сделать ее такой же прозрачной, как указатель, со weak_ptr вам нужно делать lock() каждый раз, когда вы хотите получить доступ к ресурсу Правильно? - person KaiserJohaan; 19.07.2013
comment
Как насчет передачи vec&, а затем порядкового номера, завернутого в структуру типа Handle? - person KaiserJohaan; 19.07.2013
comment
@KaiserJohaan Во-первых, редко бывает веская причина хранить ссылки в постоянных объектах. Во-вторых, прозрачность в качестве указателя бесполезна, если указатель можно сделать недействительным в любой момент. .lock() определяет область, в которой указатель нельзя сделать недействительным и его можно безопасно использовать. Это может быть излишним, если вы можете доказать, что рассматриваемый указатель будет уничтожен только в определенных точках... - person Yakk - Adam Nevraumont; 19.07.2013
comment
Нет ли хороших альтернатив, чтобы избежать необходимости выполнять .lock() каждый раз, когда вы к нему обращаетесь? - person KaiserJohaan; 19.07.2013
comment
@kaiserjohaan уверен: есть разумные правила жизни и доступа. Тот, кто владеет им, предоставляет его, и уничтожение происходит в известные моменты времени, которые не пересекаются со временем жизни указателя, не владеющего им. - person Yakk - Adam Nevraumont; 20.07.2013

Используйте простой std::vector <Model> и раздайте необработанные указатели на отдельные элементы. Вам не нужно делать это более сложным, если вам не нужен динамический полиморфизм или вы не ожидаете, что ссылки на модели переживут вектор.

person Stuart Olsen    schedule 19.07.2013
comment
Затем указатели на Model становятся недействительными при изменении размера std::vector, поэтому они не могут сохраняться в любом коде, который может изменить длину std::vector. - person Yakk - Adam Nevraumont; 20.07.2013
comment
@Yakk Ах да, об этом слишком легко забыть. Теперь, в зависимости от того, как манипулируют контейнером, std::deque <Model> может быть подходящим, но, вероятно, решение std::unique_ptr/get является лучшим. - person Stuart Olsen; 20.07.2013