Introduction: Why is caching needed in C++?
In modern high-load software, data access speed is critical. Whether it's a web server processing millions of requests or a high-frequency trading terminal, every extra database call or slow disk access can become a bottleneck. This is where caching comes to the rescue — a technique of storing frequently requested data in fast memory (RAM) for subsequent instant access.
The C++ language provides developers with unique opportunities for building efficient caching systems: from fine-grained memory control to using powerful external tools. In this article, we will journey from simple built-in solutions to industrial systems like Redis and Memcached, and analyze when and which approach to choose.
You will learn:
- How to implement a simple cache in C++ without third-party libraries.
- How to connect and use Redis and Memcached.
- A comparison of performance and use cases for each approach.
Built-in Solutions: Your Own Cache in C++
Before connecting external services, it's worth understanding that for many tasks, a built-in cache is sufficient. The main advantages: zero network latency, ease of deployment, and full control over data.
Simplest Cache with std::unordered_map
The most basic option is to use an associative array. However, it has no eviction policy and will grow indefinitely, which can lead to memory overflow.
#include <unordered_map>#include <string>
class SimpleCache { std::unordered_map<std::string, std::string> cache_;public: void put(const std::string& key, const std::string& value) { cache_[key] = value; } std::string get(const std::string& key) { auto it = cache_.find(key); if (it != cache_.end()) { return it->second; } return ""; // or throw an exception }};LRU Cache (Least Recently Used)
A more advanced solution is an LRU cache, which removes the least recently used items when the limit is reached. The implementation requires a combination of a hash table and a doubly linked list.
#include <list>#include <unordered_map>
template<typename K, typename V>class LRUCache { size_t capacity_; std::list<std::pair<K, V>> items_; std::unordered_map<K, typename std::list<std::pair<K, V>>::iterator> cache_;
public: LRUCache(size_t cap) : capacity_(cap) {}
V get(const K& key) { auto it = cache_.find(key); if (it == cache_.end()) { throw std::runtime_error("Key not found"); } items_.splice(items_.begin(), items_, it->second); return it->second->second; }
void put(const K& key, const V& value) { auto it = cache_.find(key); if (it != cache_.end()) { it->second->second = value; items_.splice(items_.begin(), items_, it->second); return; } if (items_.size() == capacity_) { auto last = items_.back(); cache_.erase(last.first); items_.pop_back(); } items_.emplace_front(key, value); cache_[key] = items_.begin(); }};Such a cache is ideal for working within a single process, when the data belongs only to that process and does not require synchronization.
Memcached: A Distributed Cache for Simple Data
Memcached is a lightweight, high-performance caching system that operates over the TCP/UDP protocol. It stores data in RAM and does not support persistence. An excellent choice for caching res