Skip to content

Commit

Permalink
Merge pull request #33 from vpetrigo/feature/wrap_value_to_smart_ptr
Browse files Browse the repository at this point in the history
Wrap value to smart ptr
  • Loading branch information
vpetrigo authored May 12, 2024
2 parents 8973d09 + fc98463 commit 3fc9e07
Show file tree
Hide file tree
Showing 12 changed files with 263 additions and 70 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ jobs:
strategy:
fail-fast: false
matrix:
tag: [13, 14, 15]
tag: [13, 15, 16, 17]
custom-hash-map: [OFF, ON]
env:
CC: clang-${{ matrix.tag }}
Expand Down Expand Up @@ -91,7 +91,7 @@ jobs:
strategy:
fail-fast: false
matrix:
tag: [5, 6, 7, 8, 9, 10, 11]
tag: [7, 8, 9, 10, 11, 12, 13]
custom-hash-map: [OFF, ON]
env:
CC: gcc
Expand Down
18 changes: 17 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,17 @@
cmake_minimum_required(VERSION 3.15)

project(caches
VERSION 0.0.5)
VERSION 0.1.0)

macro(add_coverage_flags target)
if (BUILD_TEST)
target_compile_options(${target} PRIVATE --coverage)

if (NOT MSVC)
target_link_options(${target} PRIVATE --coverage)
endif ()
endif ()
endmacro()

if (MSVC)
add_definitions(/MP)
Expand Down Expand Up @@ -29,6 +39,12 @@ target_include_directories(caches INTERFACE

if (BUILD_TEST)
enable_testing()
add_custom_target(coverage
COMMAND gcovr -r ${CMAKE_CURRENT_LIST_DIR} --filter "${CMAKE_CURRENT_LIST_DIR}/include" -x coverage.info
)
add_custom_target(coverage-html
COMMAND gcovr -r ${CMAKE_CURRENT_LIST_DIR} --filter "${CMAKE_CURRENT_LIST_DIR}/include" --html-details ${CMAKE_BINARY_DIR}/report/report.html
)
endif ()

if (INSTALL_CACHES)
Expand Down
21 changes: 11 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ Using this library is simple. It is necessary to include header with the cache i
and appropriate header with the cache policy if it is needed. If not then the non-special algorithm will be used (it
removes the last element which key is the last in the internal container).

Currently there is only three of them:
Currently, there is only three of them:

* `fifo_cache_policy.hpp`
* `lfu_cache_policy.hpp`
Expand All @@ -32,18 +32,21 @@ Example for the LRU policy:
#include "cache.hpp"
#include "lru_cache_policy.hpp"

// alias for easy class typing
// alias for an easy class typing
template <typename Key, typename Value>
using lru_cache_t = typename caches::fixed_sized_cache<Key, Value, caches::LRUCachePolicy>;

void foo(...) {
void foo() {
constexpr std::size_t CACHE_SIZE = 256;
lru_cache_t<std::string, int> cache(CACHE_SIZE);

cache.Put("Hello", 1);
cache.Put("world", 2);

const auto hello_value = cache.Get("Hello");
const auto world_value = cache.Get("world");

std::cout << cache.Get("Hello") << cache.Get("world") << '\n';
std::cout << *hello_value << *world_value << '\n';
// "12"
}
```
Expand All @@ -62,7 +65,7 @@ using lru_cache_t = typename caches::fixed_sized_cache<Key, Value, caches::LRUCa
// ...
lru_cache_t<std::string, std::size_t> cache{16};
cache.Put("Hello", 1);
std::cout << cache.Get("Hello") << '\n';
std::cout << *cache.Get("Hello") << '\n';
```
See `test` implementation which uses [`parallel-hashmap`](https://github.com/greg7mdp/parallel-hashmap).
Expand All @@ -74,13 +77,11 @@ The only requirement is a compatible C++11 compiler.
This project was tested in the environments listed below:
* MinGW64 ([MSYS2 project](https://msys2.github.io/))
* Clang 3.8.0
* GCC 5.3.0
* Clang 13.0+
* GCC 7+
* MSVC (VS 2015)
* FreeBSD
* Clang 3.4.1
If you have any issues with the library building, let me know please.
If you have any issues with the library building, please let me know.
# Contributing
Expand Down
2 changes: 1 addition & 1 deletion deps/googletest
Submodule googletest updated 241 files
2 changes: 1 addition & 1 deletion docs/doxygen/Doxyfile
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ PROJECT_NAME = "Caches"
# could be handy for archiving the generated documentation or if some version
# control system is used.

PROJECT_NUMBER = 0.0.5
PROJECT_NUMBER = 0.1.0

# Using the PROJECT_BRIEF tag one can provide an optional one line description
# for a project that appears at the top of each page and should give viewer a
Expand Down
2 changes: 1 addition & 1 deletion docs/doxygen/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,4 @@ These pages contain the API documentation of Caches library that is a header-onl

\see https://github.com/vpetrigo/caches to download the source code

\version 0.0.5
\version 0.1.0
41 changes: 27 additions & 14 deletions include/cache.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,28 +13,37 @@
#include <limits>
#include <memory>
#include <mutex>
#include <unordered_map>
#include <stdexcept>
#include <unordered_map>

namespace caches
{
/**
* \brief Wrapper over the given value type to allow safe returning of a value from the cache
*/
template <typename V>
using WrappedValue = std::shared_ptr<V>;

/**
* \brief Fixed sized cache that can be used with different policy types (e.g. LRU, FIFO, LFU)
* \tparam Key Type of a key (should be hashable)
* \tparam Value Type of a value stored in the cache
* \tparam Policy Type of a policy to be used with the cache
* \tparam HashMap Type of a hashmap to use for cache operations. Should have `std::unordered_map` compatible interface
* \tparam HashMap Type of a hashmap to use for cache operations. Should have `std::unordered_map`
* compatible interface
*/
template <typename Key, typename Value, template <typename> class Policy = NoCachePolicy,
typename HashMap = std::unordered_map<Key, Value>>
typename HashMap = std::unordered_map<Key, WrappedValue<Value>>>
class fixed_sized_cache
{
public:
using iterator = typename HashMap::iterator;
using const_iterator = typename HashMap::const_iterator;
using map_type = HashMap;
using value_type = typename map_type::mapped_type;
using iterator = typename map_type::iterator;
using const_iterator = typename map_type::const_iterator;
using operation_guard = typename std::lock_guard<std::mutex>;
using on_erase_cb = typename std::function<void(const Key &key, const Value &value)>;
using on_erase_cb =
typename std::function<void(const Key &key, const value_type &value)>;

/**
* \brief Fixed sized cache constructor
Expand All @@ -45,7 +54,7 @@ class fixed_sized_cache
*/
explicit fixed_sized_cache(
size_t max_size, const Policy<Key> policy = Policy<Key>{},
on_erase_cb on_erase = [](const Key &, const Value &) {})
on_erase_cb on_erase = [](const Key &, const value_type &) {})
: cache_policy{policy}, max_cache_size{max_size}, on_erase_callback{on_erase}
{
if (max_cache_size == 0)
Expand Down Expand Up @@ -96,10 +105,13 @@ class fixed_sized_cache
* the element is not presented in the cache. If pair's boolean value is true,
* returned iterator can be used to get access to the element
*/
std::pair<const_iterator, bool> TryGet(const Key &key) const noexcept
std::pair<value_type, bool> TryGet(const Key &key) const noexcept
{
operation_guard lock{safe_op};
return GetInternal(key);
const auto result = GetInternal(key);

return std::make_pair(result.second ? result.first->second : nullptr,
result.second);
}

/**
Expand All @@ -110,7 +122,7 @@ class fixed_sized_cache
* \param[in] key Get element by key
* \return Reference to the value stored by the specified key in the cache
*/
const Value &Get(const Key &key) const
value_type Get(const Key &key) const
{
operation_guard lock{safe_op};
auto elem = GetInternal(key);
Expand Down Expand Up @@ -176,7 +188,8 @@ class fixed_sized_cache
operation_guard lock{safe_op};

std::for_each(begin(), end(),
[&](const std::pair<const Key, Value> &el) { cache_policy.Erase(el.first); });
[&](const std::pair<const Key, value_type> &el)
{ cache_policy.Erase(el.first); });
cache_items_map.clear();
}

Expand All @@ -194,7 +207,7 @@ class fixed_sized_cache
void Insert(const Key &key, const Value &value)
{
cache_policy.Insert(key);
cache_items_map.emplace(std::make_pair(key, value));
cache_items_map.emplace(std::make_pair(key, std::make_shared<Value>(value)));
}

void Erase(const_iterator elem)
Expand All @@ -214,7 +227,7 @@ class fixed_sized_cache
void Update(const Key &key, const Value &value)
{
cache_policy.Touch(key);
cache_items_map[key] = value;
cache_items_map[key] = std::make_shared<Value>(value);
}

const_iterator FindElem(const Key &key) const
Expand All @@ -236,7 +249,7 @@ class fixed_sized_cache
}

private:
HashMap cache_items_map;
map_type cache_items_map;
mutable Policy<Key> cache_policy;
mutable std::mutex safe_op;
std::size_t max_cache_size;
Expand Down
1 change: 1 addition & 0 deletions test/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
macro(add_cache_test _TEST_NAME)
add_executable(${_TEST_NAME}_tests
${_TEST_NAME}_tests.cpp)
add_coverage_flags(${_TEST_NAME}_tests)
target_link_libraries(${_TEST_NAME}_tests
${GTEST_MAIN_LIBRARIES} caches)
target_include_directories(${_TEST_NAME}_tests PRIVATE ${GTEST_INCLUDE_DIRS} ${parallel-hashmap_SOURCE_DIR})
Expand Down
66 changes: 54 additions & 12 deletions test/fifo_cache_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@ template <typename Key, typename Value>
using fifo_cache_t = typename caches::fixed_sized_cache<Key, Value, caches::FIFOCachePolicy>;
#else
template <typename Key, typename Value>
using fifo_cache_t = typename caches::fixed_sized_cache<Key, Value, caches::FIFOCachePolicy,
phmap::node_hash_map<Key, Value>>;
using fifo_cache_t =
typename caches::fixed_sized_cache<Key, Value, caches::FIFOCachePolicy,
phmap::node_hash_map<Key, std::shared_ptr<Value>>>;
#endif /* CUSTOM_HASHMAP */

TEST(FIFOCache, Simple_Test)
Expand All @@ -23,17 +24,17 @@ TEST(FIFOCache, Simple_Test)
fc.Put(2, 20);

EXPECT_EQ(fc.Size(), 2);
EXPECT_EQ(fc.Get(1), 10);
EXPECT_EQ(fc.Get(2), 20);
EXPECT_EQ(*fc.Get(1), 10);
EXPECT_EQ(*fc.Get(2), 20);

fc.Put(1, 30);
EXPECT_EQ(fc.Size(), 2);
EXPECT_EQ(fc.Get(1), 30);
EXPECT_EQ(*fc.Get(1), 30);

fc.Put(3, 30);
EXPECT_THROW(fc.Get(1), std::range_error);
EXPECT_EQ(fc.Get(2), 20);
EXPECT_EQ(fc.Get(3), 30);
EXPECT_EQ(*fc.Get(2), 20);
EXPECT_EQ(*fc.Get(3), 30);
}

TEST(FIFOCache, Missing_Value)
Expand All @@ -43,7 +44,7 @@ TEST(FIFOCache, Missing_Value)
fc.Put(1, 10);

EXPECT_EQ(fc.Size(), 1);
EXPECT_EQ(fc.Get(1), 10);
EXPECT_EQ(*fc.Get(1), 10);
EXPECT_THROW(fc.Get(2), std::range_error);
}

Expand All @@ -61,7 +62,7 @@ TEST(FIFOCache, Sequence_Test)

for (size_t i = 0; i < TEST_SIZE; ++i)
{
EXPECT_EQ(fc.Get(std::to_string('0' + i)), i);
EXPECT_EQ(*fc.Get(std::to_string('0' + i)), i);
}

// replace a half
Expand All @@ -79,12 +80,12 @@ TEST(FIFOCache, Sequence_Test)

for (size_t i = 0; i < TEST_SIZE / 2; ++i)
{
EXPECT_EQ(fc.Get(std::to_string('a' + i)), i);
EXPECT_EQ(*fc.Get(std::to_string('a' + i)), i);
}

for (size_t i = TEST_SIZE / 2; i < TEST_SIZE; ++i)
{
EXPECT_EQ(fc.Get(std::to_string('0' + i)), i);
EXPECT_EQ(*fc.Get(std::to_string('0' + i)), i);
}
}

Expand Down Expand Up @@ -127,7 +128,7 @@ TEST(FIFOCache, TryGet)
{
auto element = cache.TryGet(std::to_string(i));
EXPECT_TRUE(element.second);
EXPECT_EQ(element.first->second, i);
EXPECT_EQ(*element.first, i);
}

for (std::size_t i = TEST_CASE; i < TEST_CASE * 2; ++i)
Expand All @@ -136,3 +137,44 @@ TEST(FIFOCache, TryGet)
EXPECT_FALSE(element.second);
}
}

TEST(FIFOCache, GetWithReplacement)
{
fifo_cache_t<std::string, std::size_t> cache{2};

cache.Put("1", 1);
cache.Put("2", 2);

auto element1 = cache.Get("1");
auto element2 = cache.Get("2");
EXPECT_EQ(*element1, 1);
EXPECT_EQ(*element2, 2);
cache.Put("3", 3);
auto element3 = cache.Get("3");
EXPECT_EQ(*element3, 3);

std::string replaced_key;

for (size_t i = 1; i <= 2; ++i)
{
const auto key = std::to_string(i);

if (!cache.Cached(key))
{
replaced_key = key;
}
}

EXPECT_FALSE(cache.Cached(replaced_key));
EXPECT_FALSE(cache.TryGet(replaced_key).second);
EXPECT_THROW(cache.Get(replaced_key), std::range_error);
EXPECT_EQ(*element1, 1);
EXPECT_EQ(*element2, 2);
EXPECT_EQ(*element3, 3);
}

TEST(FIFOCache, InvalidSize)
{
using test_type = fifo_cache_t<std::string, int>;
EXPECT_THROW(test_type cache{0}, std::invalid_argument);
}
Loading

0 comments on commit 3fc9e07

Please sign in to comment.