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

ポインタには1つの参照があります。さて、このポインタを取得したいとき

resource_ptr ptr = resourceManager.get("t1");

参照カウンターが増加します。したがって、このリソースをもう使用したくない場合

resourceManager.release(ptr);

この時点でこのリソースを削除したいのですが、参照カウンターの値は1です。

私は何をすべきか?

4

4 に答える 4

3

すでに述べたように、のペンダントは、std::shared_ptr実際std::weak_ptrにリソースを保持することなく、リソースを保持することを可能にします。weak_ptr<T>したがって、簡単な変換は、オブジェクトを人為的に存続させないように、マップに値としてを格納することです。

ただし、このスキームには問題があります。スペースリークです。マップ内のキーの数が減ることはありません。つまり、1000個の「リソース」をロードして999個を解放しても、マップには1000個のキーがあり、そのうち999個は役に立たない値に関連付けられています。

ただし、トリックはかなり単純です。破棄されると、登録されたオブジェクトは、それを参照しているオブジェクトに通知する必要があります。ただし、これにはいくつかの制限があります。

  • 登録後のオブジェクトの名前は変更しないでください
  • オブジェクトを複数回登録したり、登録されているすべてのオブジェクトのリストを維持したりしないでください。

最後に、オブジェクトが登録されたものが、オブジェクトが死ぬ前に死ぬ可能性があるという問題もあります...ちょっと複雑になりませんか?

だから、これが私たちの攻撃計画です:

  • インスタンスごとに1回だけ実行されるものは何ですか?コンストラクター。
  • どのようにして「受動的に」活気を確保しますか?使用する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は、オブジェクトの存続期間がthenによって管理されている場合、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 に答える
3

まず、あなたの質問に直接答えるために、これはまさに a のweak_ptr目的です。

これにより、参照カウントされたオブジェクトを「監視」することができますが、残っている参照がウィーク ポインターだけである場合は、オブジェクトを存続させることはできません。

次に、マネージャー クラスを作成しないでください。「リソースマネージャー」は何のために必要ですか? リソースの「管理」には何が必要ですか? それらを共有ポインターとして保存するため、ほとんど自分で管理できます。

「マネージャー」クラスの作成を検討するときはいつでも、立ち止まって「このクラスは実際に何をすべきか?」と自問する必要があります。次に、それを有益で具体的な名前に変更します。「リソース マネージャー」は、ありとあらゆるものである可能性があります。私たちはそれがリソースを使って何かをすることを知っていますが、その名前はそれが何であるかについても教えてくれません。ユーザーがリソースを検索できるようにするインデックスですか? リソースの存続期間を管理しますか? リソースのロードとアンロードを処理しますか? それともまったく違うもの?それともこれらすべてですか?

クラスが行うべきことを1 つ決めてから、名前がその 1 つのことを反映するように名前を変更します。

于 2012-10-28T16:23:47.010 に答える
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(後で挿入にヒントを与えるため-マップを2回検索する必要はありません)または順序付けされていないマップ(マップの順序を気にしないため)などを使用して最適化できます。コードコンパイルされていません。

使用例:

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 に答える