2012-10-28 88 views
1

我正在写资源管理器。这就是它的样子: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

这是一个非常简单的资源管理器,它使用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)完全指定的资源,只要你问的资源你都OK它正在建造。

编写一个采用文件名std :: string的函数,并返回一个加载的图像并将其传递给ResourceManager构造函数,并通过调用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()); 
+0

一个问题:关键仍然是... –

+0

没有太大的问题,如果它是一个。将Manager设置为'shared_from_this',将'T *'的创建者和'T *'的驱逐者传递给Manager,让它包含'shared_ptr'和一个清除函数,该函数同时调用'unregister'和在这个共享指针副本上'销毁',并在管理器中创建一个静态'std :: shared_ptr CreateInstance()'。作为副作用,这也可以防止遗漏被缓存。现在,对于真正的图像缓存,您还必须处理磁盘上的文件被更改的可能性,并孤立旧的缓存条目和未完成的shared_ptrs。 – Yakk

0

智能指针用于自动控制对象的生命周期。在这种情况下,它看起来像你不想自动控制,你想明确地控制它。所以不要使用智能指针。

3

首先,要直接回答你的问题,这正是weak_ptr的目的。

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

二,不要写经理类。你需要什么“资源管理器”? “管理”资源需要什么?您将它们存储为共享指针,因此它们可以自行管理。

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

决定一个该班应该做的事情,然后重新命名它,这样的名字反映了这一点。

+1

假设您有一个程序读入一堆图像,并且同一图像有时会多次载入。是一个类可以确保您只保留内存中每个映像的一个副本,而不是合理使用资源管理器?如果有更好的方法来做到这一点,它是什么? (之前我遇到过这个问题,我想到的解决方案是资源管理器) – Xymostech

+2

@Xymostech:这不是关于这个想法,而是关于*名称*。 'ResourceManager'告诉你关于这个类的什么?真的没什么。另一方面,“ResourceCache”或“ResourceLocator”会告诉你这个类是关于什么的。 – Xeo

+0

@Xymostech:这样的类可能很有用,但是给它一个有意义的名字。现在你已经描述了它的责任*,并告诉我这一点,我明白这个班的目的是什么。但是,如果你只是告诉我“这是资源管理器”,那么它不会告诉我任何事情。如果你需要这样的课程,那就给它一个解释它做什么的名字。无论何时你命名一个班级“经理”,它只是意味着“当我创建这个班级时,我实际上并不知道它是什么*,并且我仍然不确定”。只要给它一个合适的名字。 – jalf

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 
}