Header-only C++14 utility library. Demonstrates STL proficiency, templates, RAII, friend class, and thread-safe design.
- LRUCache: add
get_or_insert(key, factory)— combines get + conditional put in one call; factory is only invoked on cache miss - ThreadSafeQueue: add
push_for(value, timeout)— bounded producer can now time out instead of blocking forever; completes the push/pop API symmetry - ObjectPool: add optional
reset_fnparameter — called on object return to clear stale state before reuse; defaults tonullptrfor full backward compatibility - README: benchmark numbers updated to actual M1 measurements
- Initial release:
LRUCache,ThreadSafeQueue,ObjectPool,Optional
#include "utils/LRUCache.h"
#include "utils/ThreadSafeQueue.h"
#include "utils/ObjectPool.h"No build step required — just add include/ to your include path.
O(1) get/put using std::list + std::unordered_map. Evicts least-recently-used entry when capacity is exceeded.
LRUCache<int, std::string> cache(3);
cache.put(1, "one");
auto v = cache.get(1); // Optional<string>
cache.get_or_insert(1, []{ return "one"; }); // V — factory only called on miss
cache.contains(1); // bool
cache.size(); // size_t
cache.clear();Blocking, thread-safe queue with optional bounded capacity.
ThreadSafeQueue<int> q(/*capacity=*/256); // 0 = unbounded
q.push(42);
q.push_for(42, 50ms); // bounded producer with timeout → bool
auto v = q.pop(); // blocks until item available
auto v = q.try_pop(); // non-blocking
auto v = q.pop_for(50ms); // timeout
q.close(); // unblocks all waiting push/popPre-allocates objects and reuses them via RAII PoolGuard. Supports custom factory and reset injection. Thread-safe.
// default factory
ObjectPool<MyObj> pool(4);
// custom factory (Dependency Injection)
ObjectPool<MyObj> pool(4, []{ return new MyObj(99); });
// with reset_fn — clears state on return
ObjectPool<MyObj> pool(4,
[]{ return new MyObj(); },
[](MyObj* o){ o->reset(); });
{
auto g = pool.acquire(); // returns PoolGuard (move-only)
g->value = 42;
} // reset_fn called, then returned to pool
pool.available(); // size_tmkdir build && cd build
cmake .. -DCMAKE_BUILD_TYPE=Release
makeTargets: demo_lru, test_lru, demo_queue, test_queue, demo_pool, test_pool, bench
Environment: Apple M1 / macOS 15 / Apple Clang 21 / -O2 / C++14

| scenario | time | throughput |
|---|---|---|
| 100% hit | 32ms | 312M ops/s |
| 50% hit | 21ms | 476M ops/s |
100% hit is slower than 50% hit because every hit calls list::splice to move the node to front. A miss returns early with no splice, so 50% hit does half the work.
| mode | time | throughput |
|---|---|---|
| unbounded | 84ms | 11M msg/s |
| bounded=256 | 131ms | 7M msg/s |
Bounded is slower because producers must also wait on not_full_, adding contention on a second condition variable.
| mode | time | throughput |
|---|---|---|
| pool | 8ms | 125M ops/s |
| new/delete | 0ms | >1000M ops/s |
In low-contention single-threaded use, ObjectPool is slower than new/delete because mutex lock/unlock overhead exceeds the cost of the system allocator (which is heavily optimized on Apple Silicon). Pool's advantage appears under multi-threaded high-contention workloads where allocation costs spike.
- C++14 only — no C++17 features;
std::optionalreplaced by a minimalOptional<T>ininclude/utils/Optional.h - Header-only — all template implementations live in
.hfiles - STL only — no third-party dependencies
- Factory injection —
ObjectPoolaccepts a custom factory function (Dependency Injection pattern)
# 全部測試
./build/test_lru
./build/test_queue
./build/test_pool
# Demo
./build/demo_lru
./build/demo_queue
./build/demo_pool
# Benchmark
./build/bench
