cpp-lru-cache
Cache Library
cpp-lru-cache
Overview
cpp-lru-cache is a simple and reliable LRU cache library for C++ based on hashmap and linked list.
Details
cpp-lru-cache is a header-only LRU (Least Recently Used) cache library for C++11 and later, developed by Alexander Ponomarev. Implemented using a combination of std::unordered_map and std::list, it provides O(1) time complexity for get, put, and exists operations. The library is fully templated, supporting any key-value type combination, and ensures reliability through minimal logic using standard containers. No installation is required - simply include the header file to start using it. It supports exception safety and move semantics. The LRU algorithm automatically removes the least recently accessed elements when the cache becomes full, enabling memory-efficient operation. Since the C++ standard library doesn't provide an LRU cache implementation, this library plays a crucial role in performance optimization for many applications.
Pros and Cons
Pros
- O(1) Performance: get, put, and exists operations execute in constant time
- Header-only: Easy integration without installation requirements
- Template Support: Works with any key-value type combination
- Standards Compliant: Safe implementation using std::unordered_map and std::list
- Exception Safe: Maintains cache consistency even when exceptions occur
- Move Semantics: Leverages C++11 move semantics for efficiency
- Lightweight Design: High reliability with minimal logic
Cons
- C++11 Requirement: Cannot be used with older C++ standards
- Memory Overhead: Dual management of hashmap and linked list
- Thread Safety: Requires external synchronization in multi-threaded environments
- Limited Features: No advanced features like TTL or size-based eviction
- Type Constraints: Key types must be hashable and equality comparable
Key Links
Usage Examples
Basic Usage
#include "lru_cache.h"
#include <iostream>
#include <string>
int main() {
// Create LRU cache with maximum 3 elements
cache::lru_cache<int, std::string> cache(3);
// Insert elements
cache.put(1, "one");
cache.put(2, "two");
cache.put(3, "three");
// Retrieve elements
std::string value;
if (cache.get(1, value)) {
std::cout << "Key 1: " << value << std::endl;
}
// Check element existence
if (cache.exists(2)) {
std::cout << "Key 2 exists" << std::endl;
}
// Check cache size
std::cout << "Cache size: " << cache.size() << std::endl;
return 0;
}
String Key-Value Example
#include "lru_cache.h"
#include <iostream>
#include <string>
class StringCache {
private:
cache::lru_cache<std::string, std::string> cache_;
public:
StringCache(size_t capacity) : cache_(capacity) {}
void put(const std::string& key, const std::string& value) {
cache_.put(key, value);
std::cout << "Cached: " << key << " -> " << value << std::endl;
}
std::string get(const std::string& key) {
std::string value;
if (cache_.get(key, value)) {
std::cout << "Cache hit: " << key << " -> " << value << std::endl;
return value;
} else {
std::cout << "Cache miss: " << key << std::endl;
return "";
}
}
bool exists(const std::string& key) {
return cache_.exists(key);
}
size_t size() const {
return cache_.size();
}
void clear() {
cache_.clear();
std::cout << "Cache cleared" << std::endl;
}
};
// Usage example
int main() {
StringCache cache(5);
cache.put("user:1", "Alice");
cache.put("user:2", "Bob");
cache.put("user:3", "Charlie");
std::string user = cache.get("user:1"); // Cache hit
std::string unknown = cache.get("user:4"); // Cache miss
std::cout << "Cache size: " << cache.size() << std::endl;
return 0;
}
Custom Object Caching
#include "lru_cache.h"
#include <iostream>
#include <string>
#include <memory>
struct User {
int id;
std::string name;
std::string email;
User(int id, const std::string& name, const std::string& email)
: id(id), name(name), email(email) {}
};
class UserCache {
private:
cache::lru_cache<int, std::shared_ptr<User>> cache_;
public:
UserCache(size_t capacity) : cache_(capacity) {}
void cacheUser(std::shared_ptr<User> user) {
cache_.put(user->id, user);
std::cout << "Cached user: " << user->name << std::endl;
}
std::shared_ptr<User> getUser(int userId) {
std::shared_ptr<User> user;
if (cache_.get(userId, user)) {
std::cout << "User found in cache: " << user->name << std::endl;
return user;
} else {
std::cout << "User not in cache: " << userId << std::endl;
return nullptr;
}
}
bool hasUser(int userId) {
return cache_.exists(userId);
}
void printCacheInfo() {
std::cout << "Cache size: " << cache_.size() << std::endl;
}
};
// Usage example
int main() {
UserCache userCache(3);
// Create and cache users
auto alice = std::make_shared<User>(1, "Alice", "[email protected]");
auto bob = std::make_shared<User>(2, "Bob", "[email protected]");
auto charlie = std::make_shared<User>(3, "Charlie", "[email protected]");
userCache.cacheUser(alice);
userCache.cacheUser(bob);
userCache.cacheUser(charlie);
// Retrieve user from cache
auto cachedUser = userCache.getUser(1);
if (cachedUser) {
std::cout << "Retrieved: " << cachedUser->email << std::endl;
}
userCache.printCacheInfo();
return 0;
}
File Cache Implementation
#include "lru_cache.h"
#include <iostream>
#include <string>
#include <fstream>
#include <sstream>
class FileCache {
private:
cache::lru_cache<std::string, std::string> cache_;
std::string loadFileContent(const std::string& filename) {
std::ifstream file(filename);
if (!file.is_open()) {
throw std::runtime_error("Cannot open file: " + filename);
}
std::stringstream buffer;
buffer << file.rdbuf();
return buffer.str();
}
public:
FileCache(size_t capacity) : cache_(capacity) {}
std::string getFileContent(const std::string& filename) {
std::string content;
// Search in cache
if (cache_.get(filename, content)) {
std::cout << "File served from cache: " << filename << std::endl;
return content;
}
// Load file
try {
content = loadFileContent(filename);
cache_.put(filename, content);
std::cout << "File loaded and cached: " << filename << std::endl;
return content;
} catch (const std::exception& e) {
std::cerr << "Error loading file: " << e.what() << std::endl;
return "";
}
}
bool isFileCached(const std::string& filename) {
return cache_.exists(filename);
}
void evictFile(const std::string& filename) {
// Note: cpp-lru-cache doesn't have direct removal method
// This would require cache modification or workaround
std::cout << "File eviction requested: " << filename << std::endl;
}
void printStats() {
std::cout << "Cached files: " << cache_.size() << std::endl;
}
};
// Usage example
int main() {
FileCache fileCache(5);
std::string content1 = fileCache.getFileContent("config.txt");
std::string content2 = fileCache.getFileContent("data.json");
std::string content3 = fileCache.getFileContent("config.txt"); // Cache hit
fileCache.printStats();
return 0;
}
Computation Result Caching
#include "lru_cache.h"
#include <iostream>
#include <chrono>
#include <cmath>
class ComputationCache {
private:
cache::lru_cache<double, double> cache_;
double expensiveComputation(double x) {
// Simulate expensive computation
std::this_thread::sleep_for(std::chrono::milliseconds(100));
return std::sin(x) * std::cos(x) + std::sqrt(std::abs(x));
}
public:
ComputationCache(size_t capacity) : cache_(capacity) {}
double compute(double x) {
double result;
if (cache_.get(x, result)) {
std::cout << "Computation result from cache: f(" << x << ") = " << result << std::endl;
return result;
}
auto start = std::chrono::high_resolution_clock::now();
result = expensiveComputation(x);
auto end = std::chrono::high_resolution_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
std::cout << "Computed f(" << x << ") = " << result
<< " in " << duration.count() << "ms" << std::endl;
cache_.put(x, result);
return result;
}
void benchmarkCache() {
std::vector<double> inputs = {1.0, 2.0, 3.0, 1.0, 2.0, 4.0, 1.0};
std::cout << "\n=== Cache Benchmark ===" << std::endl;
for (double input : inputs) {
compute(input);
}
std::cout << "Final cache size: " << cache_.size() << std::endl;
}
};
// Usage example
int main() {
ComputationCache compCache(3);
compCache.benchmarkCache();
return 0;
}