shared_ptr-如何忽略第一个参考

shared_ptr- how to ignore first reference?

本文关键字:第一个 参考 何忽略 ptr- shared      更新时间:2023-10-16

我正在写作资源管理器。就是这样:

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

我该怎么办?

首先,要直接回答您的问题,这正是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指针
  • 我们必须小心, do 对deStructor中仍活着的缓存有一个参考,因此我们检查了它。
  • 让我们使用 virtual destructor,只是在同一侧。

好吧,让我们继续:

// 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
}

这是一个非常简单的资源管理器,它使用feek_ptr和shared_ptr。

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 ::字符串并返回加载的图像并将其传递给ResourceManager&lt;图像>构造函数,并通过调用Manager.get(String)获取图像,以上应该有效。在多线程环境中,事情自然变得更加棘手。

get()代码可以通过使用quare_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());

智能指针用于自动控制对象的寿命。在这种情况下,看起来您不需要自动控制,您想明确控制它。因此,不要使用智能指针。

相关文章: