Я пишу менеджер ресурсов. Вот как это выглядит:
#pragma once
class IObject;
typedef std::shared_ptr<IObject> resource_ptr;
typedef std::map<std::string, resource_ptr> resources_map;
class ResourceManager
{
public:
ResourceManager(void);
~ResourceManager(void);
bool add(resource_ptr &resource);
resource_ptr get(const std::string &name);
void release(resource_ptr &ptr);
private:
resources_map resources;
};
bool ResourceManager::add(resource_ptr &resource)
{
assert(resource != nullptr);
resources_map::iterator it = resources.begin();
while(it != resources.end())
{
if(it->second == resource)
return false;
it++;
}
resources[resource->getName()] = move(resource);
return true;
}
resource_ptr ResourceManager::get(const std::string &name)
{
resources_map::iterator it = resources.find(name);
resource_ptr ret = (it != resources.end()) ? it->second : nullptr;
return ret;
}
void ResourceManager::release(resource_ptr &ptr)
{
assert(ptr);
resources_map::iterator it = resources.begin();
while(it != resources.end())
{
if(it->second == ptr) {
ptr.reset();
if(!it->second)
resources.erase(it);
return;
}
it++;
}
}
И теперь, когда я добавляю новый ресурс
resource_ptr t = resource_ptr(new Texture(renderer));
t->setName("t1");
resourceManager.add(t);
Указатель имеет одну ссылку. Теперь, когда я хочу получить этот указатель
resource_ptr ptr = resourceManager.get("t1");
счетчик ссылок увеличивается. Поэтому, когда я больше не хочу использовать этот ресурс
resourceManager.release(ptr);
Я хочу удалить этот ресурс в данный момент, но счетчик ссылок имеет значение 1.
Что я должен делать?
Вот действительно простой менеджер ресурсов, который использует слабые_пользователи и расшаренные_др.
template<typename T, typename Arg=std::string, typename Ordering = std::less<Arg>>
class ResourceManager
{
typedef std::function<std::shared_ptr<T>(Arg)> factory;
factory createT;
std::map< Arg, std::weak_ptr<T>, Ordering > cache;
public:
ResourceManager( factory creator ):createT(creator) {}
std::shared_ptr<T> get( Arg a )
{
std::shared_ptr<T> retval;
auto it = cache.find(a);
if (it != cache.end())
{
retval = it->second.lock();
}
if (retval)
return retval;
retval = createT(a);
cache[a] = retval;
return std::move(retval);
}
};
Теперь для этого необходимо, чтобы вы могли создать ресурс по его имени или, более конкретно, чтобы имя (Arg) полностью указывало ресурс, и всякий раз, когда вы запрашиваете ресурс, вы согласны с его созданием.
Напишите функцию, которая принимает имя файла std :: string, возвращает загруженное изображение и передает его в ResourceManager.< image> конструктор, и получайте изображения, вызывая manager.get (string), и вышеописанное должно работать. Естественно, в многопоточной среде все становится сложнее.
Код get () можно оптимизировать с помощью equal_range (чтобы потом дать подсказку для вставки — нет необходимости дважды искать карту) или неупорядоченных карт (потому что вам не нужно упорядочивать карту) и т. Д. Код не был скомпилирован.
Образец использования:
void DisposeImage( Image* img ); // TODO: write
Image* LoadImage( std::string s ); // TODO: write
shared_ptr<Image> ImageFactory( std::string s )
{
return shared_ptr<Image>(
LoadImage(s),
DisposeImage
);
}
ResourceManager manager( ImageFactory );
std::shared_ptr<Image> bob1 = manager.get("Bob.png");
std::shared_ptr<Image> doug1 = manager.get("Doug.png");
std::shared_ptr<Image> bob2 = manager.get("Bob.png");
Assert(bob1.get() == bob2.get());
Во-первых, чтобы прямо ответить на ваш вопрос, это именно то, что weak_ptr
для.
Это позволяет вам «смотреть» объект с подсчетом ссылок, но не поддерживая его, если слабый указатель остается единственной ссылкой.
Во-вторых, не пишите классы менеджера. Зачем вам нужен «менеджер ресурсов»? Что влечет за собой «управление» ресурсами? Вы храните их как общие указатели, чтобы они могли в значительной степени управлять собой.
Каждый раз, когда вы планируете написать класс «менеджер», вы должны остановиться и спросить себя «что этот класс на самом деле должен делать?«А затем переименуйте его во что-то информативное и конкретное.« Менеджер ресурсов »может быть чем угодно и ничем. Мы знаем, что он делает … что-то с ресурсами, но название ничего не говорит нам о какие что-то есть Это индекс, позволяющий пользователям разместить Ресурсы? Управляет ли оно временем жизни ресурсов? Управляет ли он загрузкой и выгрузкой ресурсов? Или что-то совершенно другое? Или все эти вещи?
Определиться с один вещь, которую должен сделать класс, затем переименуйте его, чтобы имя отражало эту одну вещь.
Как уже было сказано, подвеска std::shared_ptr
является std::weak_ptr
что позволяет удерживать ресурс, фактически не удерживая его. Поэтому тривиальное преобразование будет хранить weak_ptr<T>
в качестве значения на вашей карте, чтобы избежать искусственного поддержания объекта в живых …
тем не мение Существует проблема с этой схемой: утечка пространства. Количество ключей на вашей карте никогда не уменьшится. Это означает, что если вы загрузите 1000 «ресурсов» и выпустите 999 из них, ваша карта все равно будет содержать 1000 ключей, из которых 999 связаны с бесполезным значением!
Однако хитрость довольно проста: после уничтожения зарегистрированный объект должен уведомить тех, кто имеет на него ссылку! Это накладывает ряд ограничений, хотя:
Наконец, есть также проблема, из-за которой те, кто был зарегистрирован, могли умереть раньше, чем объект … Разве это не сложно?
Итак, вот наш план атаки:
std::weak_ptr
Поехали!
// Object.hpp
class Cache;
class Object: public enable_shared_from_this<Object> {
public:
// std::shared_ptr<Object> shared_from_this(); -- inherited
Object(std::string const& name, std::shared_ptr<Cache> const& cache);
virtual ~Object();
Object(Object const&) = delete;
Object& operator=(Object const&) = delete;
std::string const& name() const { return _name; }
private:
std::string _name;
std::weak_ptr<Cache> _cache;
}; // class Object
// Object.cpp
#include <Object.hpp>
#include <Cache.hpp>
Object::Object(std::string const& name, std::shared_ptr<Cache> const& cache):
_name(name), _cache(cache)
{
if (cache) { cache->add(this->shared_from_this()); }
}
Object::~Object() {
std::shared_ptr<Cache> c = _cache.lock();
if (c) { c->release(*this); }
}
Пара странных вещей, происходящих здесь:
const_cast
…)enable_shared_from_this
означает, что если время жизни объекта управляется shared_ptr
затем с помощью shared_from_this
мы можем получить shared_ptr
указатель на этоvirtual
деструктор, просто чтобы быть на одной стороне.Итак, давайте продолжим:
// Cache.hpp
#include <Object.hpp>
class Cache: public std::enable_shared_from_this<Cache> {
friend class Object;
public:
// std::shared_ptr<Cache> shared_from_this(); -- inherited
std::shared_ptr<Object> get(std::string const& name) const;
void release(Object const& o);
private:
typedef std::weak_ptr<Object> WeakPtr;
typedef std::map<std::string, WeakPtr> Map;
void add(std::shared_ptr<Object> const& p);
Map _map;
}; // class Cache
// Cache.cpp
#include <Cache.hpp>
std::shared_ptr<Object> Cache::get(std::string const& name) const {
auto const it = _map.find(name);
if (it == _map.end()) { return std::shared_ptr<Object>(); }
return it->second.lock();
}
void Cache::release(Object const& o) {
_map.erase(o.name());
}
void Cache::add(std::shared_ptr<Object> const& p) {
assert(p && "Uh ? Should only be accessed by Object's constuctor!");
_map[p->name()] = p; // Note: override previous resource of same name, if any
}
Это кажется довольно легким сейчас. Использование:
int main() {
std::shared_ptr<Cache> c{new Cache{}}; // cannot be stack allocated no longer
{
std::shared_ptr<Object> o{new Object{"foo", c}};
assert(c->get("foo"));
}
assert(c->get("foo") == nullptr);
std::shared_ptr<Object> o{new Object{"foo", c}};
c.reset(); // destroy cache
// no crash here, we just do not "unregister" the object
}
Умные указатели используются для автоматического управления временем жизни объекта. В этом случае, похоже, вам не нужно автоматическое управление, вы хотите управлять им явно. Так что не используйте умный указатель.