我正在写作资源管理器。就是这样:
#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());
智能指针用于自动控制对象的寿命。在这种情况下,看起来您不需要自动控制,您想明确控制它。因此,不要使用智能指针。