简体   繁体   中英

C++: Reference counting values in a std::map; is std::multimap a better choice?

Currently I have implemented a reference-counting cache of the values of the map like so:

//filename or name of bitmap, reference count, memory location...
std::map<std::string, std::pair<long, BITMAP*> > _cache;

Is using a std::multimap a better choice?

//filename or name of bitmap, memory location...
std::multimap<std::string, BITMAP*> _cache;

Or just a different way of doing it?

-- EDIT --

Here's the specific class for clarity of my intentions It is intended to be a private class that is strictly utility to the rest of the code that the user never sees. To them, they are just creating a Sprite. NOTE: The BITMAP struct is considered private and the only way to create/destroy/modify one is through one of the many functions in the 3rd-party C library that REQUIRE the use of raw pointers.

BitmapCache.h

#ifndef A2DE_CBITMAPCACHE_H
#define A2DE_CBITMAPCACHE_H

#include "../a2de_vals.h"
#include <allegro/file.h>
#include <allegro/gfx.h>
#include <allegro/draw.h>
#include <allegro/datafile.h>
#include <allegro/color.h>

#include <map>
#include <utility>
#include <string>

struct BITMAP;

_A2DE_BEGIN

class BitmapCache {
public:
    static BITMAP* GetBitmap(std::string filename);
    static BITMAP* StoreBitmap(std::string name, BITMAP* bmp);
    static BITMAP* RetrieveBitmap(std::string name);
    static std::string GetBitmapName(BITMAP* file);
    static void RemoveBitmap(std::string name);

protected:
private:
    static std::map<std::string, std::pair<long, BITMAP*> > _cache;
    static void CleanCache();

};

_A2DE_END

#endif

BitmapCache.cpp

#include "CBitmapCache.h"

#include <algorithm>
#include <map>

_A2DE_BEGIN

//filename or name of bitmap, reference count, memory location...
typedef std::map<std::string, std::pair<long, BITMAP*> > MapStrBmp;
typedef MapStrBmp::iterator MapStrBmpIter;

MapStrBmp BitmapCache::_cache;

BITMAP* BitmapCache::GetBitmap(std::string filename) {
    //Return NULL if a bad filename was passed.
    if(filename.empty()) return NULL;
    if(exists(filename.c_str()) == false) return NULL;

    //Reduce incorrect results by forcing slash equality.
    filename = fix_filename_slashes(&filename[0]);

    //Clean the cache if it's dirty.
    CleanCache();

    //Search for requested BITMAP.
    MapStrBmpIter _iter = _cache.find(filename);

    //If found, return it.
    if(_iter != _cache.end()) {
        _iter->second.first++;
        return _iter->second.second;
    }

    //Otherwise, create it, store it, then return it.
    BITMAP* result = load_bmp(filename.c_str(), NULL);
    if(result == NULL) return NULL;
    _cache.insert(std::make_pair(filename, std::make_pair(static_cast<long>(1), result)));
    return result;
}

BITMAP* BitmapCache::StoreBitmap(std::string name, BITMAP* bmp) {
    if(name.empty() || bmp == NULL) return NULL;

    CleanCache();
    name = fix_filename_slashes(&name[0]);
    MapStrBmpIter _iter = _cache.find(name);
    if(_iter != _cache.end()) {
        _iter->second.first++;
        return _iter->second.second;
    }

    _cache.insert(std::make_pair(name, std::make_pair(static_cast<long>(1), bmp)));
    return bmp;
}
BITMAP* BitmapCache::RetrieveBitmap(std::string name) {
    if(name.empty()) return NULL;

    name = fix_filename_slashes(&name[0]);
    MapStrBmpIter _iter = _cache.find(name);
    if(_iter != _cache.end()) {
        _iter->second.first++;
        return _iter->second.second;
    }
    return NULL;
}

void BitmapCache::RemoveBitmap(std::string name) {
    if(name.empty()) return;

    name = fix_filename_slashes(&name[0]);
    MapStrBmpIter _iter = _cache.find(name);

    if(_iter != _cache.end()) {
        _iter->second.first--;
        CleanCache();
    }
}

std::string BitmapCache::GetBitmapName(BITMAP* file) {
    if(file == NULL) return std::string("");

    CleanCache();
    MapStrBmpIter b = _cache.begin();
    MapStrBmpIter e = _cache.end();
    for(MapStrBmpIter _iter = b; _iter != e; ++_iter) {
        if(_iter->second.second != file) continue;
        return _iter->first;
    }
    return std::string("");
}

void BitmapCache::CleanCache() {

    //Clean the cache of any bitmaps that are no longer referenced.
    MapStrBmpIter b = _cache.begin();
    MapStrBmpIter e = _cache.end();
    for(MapStrBmpIter _iter = b; _iter != e; /* DO NOTHING */ ) {
        if(_iter->second.first > 0) {
            ++_iter;
            continue;
        }
        destroy_bitmap(_iter->second.second);
        _iter->second.second = NULL;
        _cache.erase(_iter++);
    }
}

_A2DE_END
std::map<std::string, std::pair<long, BITMAP*> > _cache;

DON'T reinvent the wheel. Use shared_ptr (available in boost or in tr1 namespace in certain compilers or in std:: namespace in newer compilers) or any other existing well-tested smart pointer classes. Reinventing the wheel is one of the common programming mistakes - by trying to reimplement something (that has been already written by somebody else) you'll waste development time and gain nothing.

--EDIT--

and destroy_bitmap methods

boost::shared_ptr supports custom deleters. Use them.

how would I do it so several BITMAPs are not created when the same filename is passed in

std::map<std::string, boost::weak_ptr<BITMAP> > . If value does not exist in map, or existing weak_ptr has expired, create new shared_ptr with deleter and put weak_ptr to it into map and return this shared_ptr. Otherwise (weak_ptr has not expired) extract shared_ptr from weak_ptr and return it.

Of course, that depends on usage pattern. If you aren't writing some kind of "resource cache/pool" (unused resources are deleted even if they're within map), then you can use shared_ptr with deleters within map.

Assuming you have:

struct BITMAP;
BITMAP* create_bitmap(std::string const& filename);
void destroy_bitmap(BITMAP*);

then you can have:

typedef std::shared_ptr<BITMAP> bitmap_ptr;

class bitmap_cache {
public:
    bitmap_ptr
    make(std::string const& filename)
    {
        auto it = map.find(filename);
        if(it != map.end()) {
            return it->second;
        } else {
            bitmap_ptr p(create_bitmap(filename), &destroy_bitmap);
            map.insert(std::make_pair(filename, p));
            return p;
        }
    }

private:
    std::map<std::string, bitmap_ptr> map;
};

Note that each bitmap_cache maintain all of the created BITMAP s alive during their lifetime, but unused BITMAP s will properly be disposed of when a cache reaches the end of its lifetime ( BITMAP s in use by some client of the cache will remain safely alive). You can improve on that by using std::weak_ptr<BITMAP> in the map instead if you need that.

Well, it turns out I can't use STL smart pointers with what I am trying to do. After re-working the above code to use smart pointers the program kept crashing on close. It turns out that the allegro 4.2 library API specifically states that attempting to call allegro methods (specifically ones that deal with bitmaps...) after allegro has been deinitialized will crash the program. Smart pointers only destroy themselves (and try to call their contained pointer's deleter methods) after they've run out of scope...after the program has ended and allegro has been deinitialized, which in turn crashes the program.

So for now, the current solution of rolling my own implementation of reference counting is the work-around.

Thanks for the advice and the help anyway.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM