1

我正在写资源管理器。这就是它的样子:

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

我该怎么办?

4

4 回答 4

3

首先,要直接回答您的问题,这正是 aweak_ptr的用途。

它允许您“观察”一个引用计数的对象,但如果弱指针是唯一剩下的引用,则不会使其保持活动状态。

其次,不要编写管理器类。你需要一个“资源管理器”做什么?“管理”资源意味着什么?您将它们存储为共享指针,因此它们几乎可以自行管理。

任何时候你考虑写一个“经理”类,你应该停下来问问自己“这个类实际上应该做什么?” 然后将其重命名为内容丰富且具体的内容。“资源管理器”可以是任何东西,也可以什么都不是。我们知道它确实... 有资源的东西,但这个名字并没有告诉我们那个东西是什么。它是一个允许用户定位资源的索引吗?它是否管理资源的生命周期?它是否处理资源的加载和卸载?还是完全不同的东西?还是所有这些东西?

决定类应该做的件事,然后重命名它,以便名称反映那一件事。

于 2012-10-28T16:23:47.010 回答
3

如前所述,std::shared_ptris的吊坠std::weak_ptr允许在不实际持有资源的情况下持有资源。因此,一个简单的转换是将 aweak_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意味着如果对象的生命周期由 a 管理,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
}
于 2012-10-28T19:07:33.980 回答
1

这是一个非常简单的资源管理器,它使用了weak_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::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());
于 2012-10-28T17:36:37.983 回答
0

智能指针用于自动控制对象的生命周期。在这种情况下,您似乎不想自动控制,而是想明确控制它。所以不要使用智能指针。

于 2012-10-28T16:22:00.303 回答