如何使用std::shared_ptr实现缓存管理器

How to implement cache manager using std::shared_ptr?

本文关键字:实现 缓存 管理器 ptr shared 何使用 std      更新时间:2023-10-16
#include <mutex>
#include <assert.h>
#include <iostream>
#include <unordered_map>
#include <memory>
#include <string>
#include <stdio.h>
// 
// Requirements:
//  1: the bitmap could be used by multiple thread safely.(std::shared_ptr could?)
//  2: cache the bitmap and do not always increase memeory
//@NotThreadSfe
struct Bitmap {
    public:
        Bitmap(const std::string& filePath) { 
            filePath_ = filePath;
            printf("foo %x ctor %sn", this, filePath_.c_str());
        }
        ~Bitmap() {
            printf("foo %x dtor %sn", this, filePath_.c_str());
        }
        std::string filePath_;
};
//@ThreadSafe
struct BitmapCache {
    public:
        static std::shared_ptr<Bitmap> loadBitmap(const std::string& filePath) {
            mutex_.lock();
            //whether in the cache
            auto iter = cache_.find(filePath);
            if (iter != cache_.end()) {
                if ((*iter).second) {
                    return (*iter).second;
                } else {
                    std::shared_ptr<Bitmap> newPtr(new Bitmap(filePath));
                    (*iter).second = newPtr;
                    return newPtr;
                }
            }
            //try remove unused elements if possible
            if (cache_.size() >= kSlotThreshold) {
                std::unordered_map<std::string,std::shared_ptr<Bitmap>>::iterator delIter = cache_.end();
                for (auto iter = cache_.begin(); iter != cache_.end(); ++iter) {
                    auto& item = *iter;
                    if (item.second && item.second.use_count() == 1) {
                        delIter = iter;
                        break;
                    }
                }
                if (cache_.end() != delIter) {
                    (*delIter).second.reset();
                    cache_.erase(delIter);
                }
            }
            //create new and insert to the cache
            std::shared_ptr<Bitmap> newPtr(new Bitmap(filePath));
            cache_.insert({filePath, newPtr});
            mutex_.unlock();
            return newPtr;
        }
    private:
        static const int kSlotThreshold = 20;
        static std::mutex mutex_;
        static std::unordered_map<std::string,std::shared_ptr<Bitmap>> cache_;
};
/* static */
std::unordered_map<std::string,std::shared_ptr<Bitmap>> BitmapCache::cache_;
/* static */
std::mutex BitmapCache::mutex_;
int main()
{
    //test for remove useless element
    char buff[200] = {0};
    std::vector<std::shared_ptr<Bitmap>> bmpVec(20);
    for (int i = 0; i < 20; ++i) {
        sprintf_s(buff, 200, "c:\haha%d.bmp", i);
        bmpVec[i] = BitmapCache::loadBitmap(buff);
    }
    bmpVec[3].reset();
    std::shared_ptr<Bitmap> newBmp = BitmapCache::loadBitmap("c:\new.bmp");
    //test for multiple threading...(to be implemenetd)
    return 0;
}

我是c++内存管理的新手。你能给我一个提示吗:我的方法是正确的吗?或者我应该采用不同的设计策略或不同的内存管理器策略(如weak_ptr等)?

这让我想起了Herb Sutter在2013年GoingNative大会上的"最喜欢的c++ 10-Liner"演讲,稍作改编:

std::shared_ptr<Bitmap> get_bitmap(const std::string & path){
    static std::map<std::string, std::weak_ptr<Bitmap>> cache;
    static std::mutex m;
    std::lock_guard<std::mutex> hold(m);
    auto sp = cache[path].lock();
    if(!sp) cache[path] = sp = std::make_shared<Bitmap>(path);
    return sp;
}

的评论:

  1. 总是使用std::lock_guard,而不是调用lock()unlock()。后者更容易出错,也不例外安全。注意,在您的代码中,前两个返回语句从未解锁互斥锁。
  2. 这里的想法是跟踪分配的位图对象与weak_ptr在缓存中,所以缓存永远不会保持一个位图本身是活的。这消除了对不再使用的对象进行手动清理的需要——当最后一个引用它们的shared_ptr被销毁时,它们将被自动删除。
  3. 如果path之前从未出现过,或者对应的Bitmap已经被删除,cache[path].lock()将返回空的shared_ptr;下面的if语句然后用make_shared加载Bitmap,将结果shared_ptr移赋给sp,并设置cache[path]来跟踪新创建的位图。
  4. 如果path对应的Bitmap仍然活着,那么cache[path].lock()将创建一个新的shared_ptr引用它,然后返回。