Cache arbitrary key-value pairs with this simple utility class.
- 🏷️ Flexible Key and Value Types
Cache any data type with customizable key types. - 🎯 Exact or Approximate Matching
Retrieve cached values with precise or tolerance-based policies. - 🕑 Time-Based Keys
Perfect for applications needing time-point based caching. - ⚙ Configurable Matching Policies
Define individual policies for value retrieval. - 🐍 Python Bindings
Seamlessly use the library in Python with pybind11 bindings. - 🧪 Tested and Reliable
Includes unit tests to ensure robustness in real-world applications. - 📦 Header-Only
Easy integration into your project – just include the headers! - 📜 Permissive License
Published under the MIT license to maximize your project's flexibility.
To get you started, here are some examples of how to use this library. Make sure to check out the unit tests for more detailed examples.
Caching with number
You could cache a value with arbitrary type (e.g. double) giving number (e.g. double) as key, first value type is for the key.
Cache<double, double> cacheByNumber;
double key1 = 1.0;
cacheByNumber.cache(key1, 2.0);
Now the Cache stores the value 2.0
with key 1.0
. You could restore the value by matching the exact key. Which will return true
and you can obtain the value by referencing with .value()
// exact match
EXPECT_TRUE(cacheByNumber.cached(key1));
EXPECT_DOUBLE_EQ(cacheByNumber.cached(key1).value(), 1.);
You can also do approximate matching by specifying one matching policy
util_caching::policies::ApproximateNumber<double> approximateNumberPolicy{0.5};
with 0.5
threshold that the key should not differ with the stored key. Now you can recall the stored value giving the key:
double key2{1.2};
// approximate match
EXPECT_TRUE(cacheByNumber.cached(key2, approximateNumberPolicy));
EXPECT_DOUBLE_EQ(cacheByNumber.cached(key2, approximateNumberPolicy).value(), 1.);
Of course the value can not be recalled when the keys differ more than the threshold:
double key3{1.6};
// over threshold
EXPECT_FALSE(cacheByNumber.cached(key3, approximateNumberPolicy));
Caching with time point
A more practical usage is to cache values by giving time point as key:
using Time = std::chrono::steady_clock::time_point;
Cache<Time, double> cacheByTime;
Time time1 = std::chrono::steady_clock::now();
cacheByTime.cache(time1, 1.);
Now you can recall the value by either do exact match:
// exact match
EXPECT_TRUE(cacheByTime.cached(time1));
EXPECT_DOUBLE_EQ(cacheByTime.cached(time1).value(), 1.);
or by specifying one comparison policy and threshold (100ms for example), and recall by approximate match:
Time time2 = time1 + 10ms;
util_caching::policies::ApproximateTime<Time, std::chrono::milliseconds> approximateTimePolicy{100};
// approximate match with miliseconds
EXPECT_TRUE(cacheByTime.cached(time2, approximateTimePolicy));
EXPECT_DOUBLE_EQ(cacheByTime.cached(time2, approximateTimePolicy).value(), 1.);
Using the Python bindings
The library can be used in Python via pybind11 bindings.
Since util_caching
is a templated C++ library,
you need to explicitly instantiate the template for the types you want to use in Python.
For this, we provide convenience functions to bind the library for the desired types.
Simply call them in a pybind11 module definition, e.g.:
PYBIND11_MODULE(util_caching, m) {
python_api::number_based::bindCache<double, double>(m);
}
and use them in Python:
from util_caching import Cache
cache = Cache()
cache.cache(1.0, 2.0)
We re-implemented all of the C++ unit tests in Python, so take a closer look at those for more advanced usage examples.
Using Debian package (recommended)
We provide a Debian package for easy installation on Debian-based distributions.
Download the latest .deb
package and install it with dpkg
:
sudo dpkg -i libutil-caching-dev.deb
Using Docker image
We provide a Dockerfile
with the library already installed globally.
Clone or download this repository, then build and run the docker image with docker compose
:
cd util_caching
docker compose build
docker compose run --rm util_caching
The library is installed in the Docker image under /usr/local/include/util_caching/
and /usr/local/lib/cmake/util_caching/
.
So, it can be easily loaded with CMake:
find_package(util_caching REQUIRED)
Building from source using CMake
First make sure all dependencies are installed:
- Googletest (optional, if you want to build unit tests)
- pybind11 (optional, if you want to build Python bindings and unit tests)
See also the Dockerfile
for how to install these packages under Debian or Ubuntu.
Compile and install the project with CMake:
mkdir -p util_caching/build
cd util_caching/build
cmake ..
cmake --build .
sudo cmake --install .
Using Docker image
To start a development container, run:
docker compose run --rm --build util_caching_devel
This mounts the source into the container's /home/blinky/util_caching
folder.
There, you can edit the source code, compile and run the tests etc.
Compiling unit tests using CMake
In order to compile with tests define BUILD_TESTS=true
mkdir -p util_caching/build
cd util_caching/build
cmake -DBUILD_TESTS=true ..
cmake --build .
Run all unit tests:
cmake --build . --target test
Using ROS 1 and catkin
The demo/Dockerfile_ros
shows that how util_caching can be used in a catkin project (it uses CMake under the hood anyways):
docker compose -f demo/docker-compose.ros.yaml build
docker compose -f demo/docker-compose.ros.yaml run --rm util_caching_ros
See demo/README.md for how to run the demo, showcasing the use of util_caching
in a ROS node.