shared_ptr — как игнорировать первую ссылку?

Я пишу менеджер ресурсов. Вот как это выглядит:

#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.

Что я должен делать?

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());
1

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

Во-первых, чтобы прямо ответить на ваш вопрос, это именно то, что weak_ptr для.

Это позволяет вам «смотреть» объект с подсчетом ссылок, но не поддерживая его, если слабый указатель остается единственной ссылкой.

Во-вторых, не пишите классы менеджера. Зачем вам нужен «менеджер ресурсов»? Что влечет за собой «управление» ресурсами? Вы храните их как общие указатели, чтобы они могли в значительной степени управлять собой.

Каждый раз, когда вы планируете написать класс «менеджер», вы должны остановиться и спросить себя «что этот класс на самом деле должен делать?«А затем переименуйте его во что-то информативное и конкретное.« Менеджер ресурсов »может быть чем угодно и ничем. Мы знаем, что он делает … что-то с ресурсами, но название ничего не говорит нам о какие что-то есть Это индекс, позволяющий пользователям разместить Ресурсы? Управляет ли оно временем жизни ресурсов? Управляет ли он загрузкой и выгрузкой ресурсов? Или что-то совершенно другое? Или все эти вещи?

Определиться с один вещь, которую должен сделать класс, затем переименуйте его, чтобы имя отражало эту одну вещь.

3

Как уже было сказано, подвеска 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
}
3

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

0
По вопросам рекламы ammmcru@yandex.ru
Adblock
detector