/////////////////////////////////////////////////////////////////////////////// // Tyler Perkins // 8-23-21 // LRU cache implementation // #ifndef _LRUCACHE_HPP_ #define _LRUCACHE_HPP_ #include #include #include #include namespace clortox { constexpr size_t __default_max_cache_size = 50; template struct value_iterator { typename std::list::iterator it; T value; }; template> class LRUCache{ public: LRUCache(); LRUCache(size_t); ~LRUCache(); LRUCache(const LRUCache&); LRUCache& operator=(const LRUCache&); void swap(LRUCache&); //utility void clear (); inline bool empty () const; inline bool full () const; inline size_t getMaxCacheSize() const; //accessors functions T& get (const Key&); inline T& operator[](const Key&); inline T& operator[](Key&); void put (const Key&, const T&); bool erase (const Key&); void pop_back (); #ifdef _LRU_DEBUG_ bool wasHit = false; #endif private: //list std::list _list; //hashmap std::unordered_map, Hash> _hash_map; size_t _max_cache_size; }; } /////////////////////////////////////////////////////////////////////////////// // Implementation ///////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////// // Default Ctor template clortox::LRUCache::LRUCache() : LRUCache(__default_max_cache_size) {}; template clortox::LRUCache::LRUCache( size_t max_size) : _max_cache_size(max_size){ _hash_map.reserve(max_size); } /////////////////////////////////////// // Dector template clortox::LRUCache::~LRUCache(){ while(!empty()){ pop_back(); } } /////////////////////////////////////// // Copy ctor template clortox::LRUCache::LRUCache(const LRUCache& rhs){ _max_cache_size = rhs._max_cache_size; _hash_map = rhs._hash_map; _list = rhs._list; } /////////////////////////////////////// // Assignment operator template clortox::LRUCache& clortox::LRUCache< Key, T, Hash>::operator=(const LRUCache& rhs){ if(this != &rhs){ _max_cache_size = rhs._max_cache_size; _hash_map = rhs._hash_map; _list = rhs._list; } return *this; } /////////////////////////////////////// // Swap function template void clortox::LRUCache::swap(LRUCache& rhs){ decltype(_hash_map) tmphashmap = rhs._hash_map; rhs._hash_map = _hash_map; _hash_map = tmphashmap; decltype(_list) tmplist = rhs._list; rhs._list = _list; _list = tmplist; decltype(_max_cache_size) tmpmax = rhs._max_cache_size; rhs._max_cache_size = _max_cache_size; _max_cache_size = tmpmax; } /////////////////////////////////////// // Clears out all elements from cache // This should only be called if all // elements in the cache are not likely // to be used again template void clortox::LRUCache::clear(){ for(auto it = _hash_map.begin(); it != _hash_map.end();){ _list.erase(it->second.it); it = _hash_map.erase(it); } } /////////////////////////////////////// // Returns true if the cache is empty template bool clortox::LRUCache::empty() const{ return _list.empty(); } /////////////////////////////////////// // Check if cache is full template bool clortox::LRUCache::full() const{ return _list.size() == _max_cache_size; } /////////////////////////////////////// // Returns the max cache size template size_t clortox::LRUCache::getMaxCacheSize() const{ return _max_cache_size; } /////////////////////////////////////////////////////////////////////////////// // Accessor functions ///////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////// // get element from cache. If it does // not exist, create it template T& clortox::LRUCache::get(const Key& key){ if(_hash_map.find(key) != _hash_map.end()){ //cache hit #ifdef _LRU_DEBUG_ wasHit = true; #endif //update list _list.erase(_hash_map[key].it); _list.push_front(key); _hash_map[key].it = _list.begin(); return _hash_map[key].value; } else { //cache miss #ifdef _LRU_DEBUG_ wasHit = false; #endif //if cache is full, remove oldest element if(_list.size() == _max_cache_size){ _hash_map.erase(_list.back()); _list.pop_back(); } //add new element _list.push_front(key); _hash_map[key] = { .it = _list.begin(), .value = T(key) }; return _hash_map[key].value; } } /////////////////////////////////////// // Index operator returns value with // assoc key. If it does not exist, we // create it template T& clortox::LRUCache::operator[](const Key& key){ return get(key); } template T& clortox::LRUCache::operator[](Key& key){ return get(key); } /////////////////////////////////////// // Puts the key value pair into the // cache template void clortox::LRUCache::put(const Key& key, const T& value){ if(_hash_map.find(key) != _hash_map.end()){ //cache hit #ifdef _LRU_DEBUG_ wasHit = true; #endif //update list _list.erase(_hash_map[key].it); _list.push_front(key); _hash_map[key].it = _list.begin(); _hash_map[key].value = value; } else { //cache hit, insert new element #ifdef _LRU_DEBUG_ wasHit = false; #endif //if cache is full, remove oldest element if(_list.size() == _max_cache_size){ _hash_map.erase(_list.back()); _list.pop_back(); } //add new element _list.push_front(key); _hash_map[key] = { .it = _list.begin(), .value = value }; } } /////////////////////////////////////// // Remove element from cache. Only do // this if positive it will not be // referenced again template bool clortox::LRUCache::erase(const Key& key){ if(_hash_map.find(key) != _hash_map.end()){ //cache hit //remove element _list.erase(_hash_map[key].it); _hash_map.erase(key); return true; } //not in cache, return false return false; } /////////////////////////////////////// // Remove oldest element template void clortox::LRUCache::pop_back(){ if(!_list.empty()){ _hash_map.erase(_list.back()); _list.pop_back(); } } #endif //!_LRUCACHE_HPP_