Skip to content

Commit 3fc9e07

Browse files
authored
Merge pull request #33 from vpetrigo/feature/wrap_value_to_smart_ptr
Wrap value to smart ptr
2 parents 8973d09 + fc98463 commit 3fc9e07

File tree

12 files changed

+263
-70
lines changed

12 files changed

+263
-70
lines changed

.github/workflows/ci.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ jobs:
5959
strategy:
6060
fail-fast: false
6161
matrix:
62-
tag: [13, 14, 15]
62+
tag: [13, 15, 16, 17]
6363
custom-hash-map: [OFF, ON]
6464
env:
6565
CC: clang-${{ matrix.tag }}
@@ -91,7 +91,7 @@ jobs:
9191
strategy:
9292
fail-fast: false
9393
matrix:
94-
tag: [5, 6, 7, 8, 9, 10, 11]
94+
tag: [7, 8, 9, 10, 11, 12, 13]
9595
custom-hash-map: [OFF, ON]
9696
env:
9797
CC: gcc

CMakeLists.txt

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,17 @@
11
cmake_minimum_required(VERSION 3.15)
22

33
project(caches
4-
VERSION 0.0.5)
4+
VERSION 0.1.0)
5+
6+
macro(add_coverage_flags target)
7+
if (BUILD_TEST)
8+
target_compile_options(${target} PRIVATE --coverage)
9+
10+
if (NOT MSVC)
11+
target_link_options(${target} PRIVATE --coverage)
12+
endif ()
13+
endif ()
14+
endmacro()
515

616
if (MSVC)
717
add_definitions(/MP)
@@ -29,6 +39,12 @@ target_include_directories(caches INTERFACE
2939

3040
if (BUILD_TEST)
3141
enable_testing()
42+
add_custom_target(coverage
43+
COMMAND gcovr -r ${CMAKE_CURRENT_LIST_DIR} --filter "${CMAKE_CURRENT_LIST_DIR}/include" -x coverage.info
44+
)
45+
add_custom_target(coverage-html
46+
COMMAND gcovr -r ${CMAKE_CURRENT_LIST_DIR} --filter "${CMAKE_CURRENT_LIST_DIR}/include" --html-details ${CMAKE_BINARY_DIR}/report/report.html
47+
)
3248
endif ()
3349

3450
if (INSTALL_CACHES)

README.md

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ Using this library is simple. It is necessary to include header with the cache i
1919
and appropriate header with the cache policy if it is needed. If not then the non-special algorithm will be used (it
2020
removes the last element which key is the last in the internal container).
2121

22-
Currently there is only three of them:
22+
Currently, there is only three of them:
2323

2424
* `fifo_cache_policy.hpp`
2525
* `lfu_cache_policy.hpp`
@@ -32,18 +32,21 @@ Example for the LRU policy:
3232
#include "cache.hpp"
3333
#include "lru_cache_policy.hpp"
3434

35-
// alias for easy class typing
35+
// alias for an easy class typing
3636
template <typename Key, typename Value>
3737
using lru_cache_t = typename caches::fixed_sized_cache<Key, Value, caches::LRUCachePolicy>;
3838

39-
void foo(...) {
39+
void foo() {
4040
constexpr std::size_t CACHE_SIZE = 256;
4141
lru_cache_t<std::string, int> cache(CACHE_SIZE);
4242

4343
cache.Put("Hello", 1);
4444
cache.Put("world", 2);
45+
46+
const auto hello_value = cache.Get("Hello");
47+
const auto world_value = cache.Get("world");
4548

46-
std::cout << cache.Get("Hello") << cache.Get("world") << '\n';
49+
std::cout << *hello_value << *world_value << '\n';
4750
// "12"
4851
}
4952
```
@@ -62,7 +65,7 @@ using lru_cache_t = typename caches::fixed_sized_cache<Key, Value, caches::LRUCa
6265
// ...
6366
lru_cache_t<std::string, std::size_t> cache{16};
6467
cache.Put("Hello", 1);
65-
std::cout << cache.Get("Hello") << '\n';
68+
std::cout << *cache.Get("Hello") << '\n';
6669
```
6770
6871
See `test` implementation which uses [`parallel-hashmap`](https://github.com/greg7mdp/parallel-hashmap).
@@ -74,13 +77,11 @@ The only requirement is a compatible C++11 compiler.
7477
This project was tested in the environments listed below:
7578
7679
* MinGW64 ([MSYS2 project](https://msys2.github.io/))
77-
* Clang 3.8.0
78-
* GCC 5.3.0
80+
* Clang 13.0+
81+
* GCC 7+
7982
* MSVC (VS 2015)
80-
* FreeBSD
81-
* Clang 3.4.1
8283
83-
If you have any issues with the library building, let me know please.
84+
If you have any issues with the library building, please let me know.
8485
8586
# Contributing
8687

deps/googletest

Submodule googletest updated 241 files

docs/doxygen/Doxyfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ PROJECT_NAME = "Caches"
3838
# could be handy for archiving the generated documentation or if some version
3939
# control system is used.
4040

41-
PROJECT_NUMBER = 0.0.5
41+
PROJECT_NUMBER = 0.1.0
4242

4343
# Using the PROJECT_BRIEF tag one can provide an optional one line description
4444
# for a project that appears at the top of each page and should give viewer a

docs/doxygen/index.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,4 @@ These pages contain the API documentation of Caches library that is a header-onl
1717

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

20-
\version 0.0.5
20+
\version 0.1.0

include/cache.hpp

Lines changed: 27 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -13,28 +13,37 @@
1313
#include <limits>
1414
#include <memory>
1515
#include <mutex>
16-
#include <unordered_map>
1716
#include <stdexcept>
17+
#include <unordered_map>
1818

1919
namespace caches
2020
{
21+
/**
22+
* \brief Wrapper over the given value type to allow safe returning of a value from the cache
23+
*/
24+
template <typename V>
25+
using WrappedValue = std::shared_ptr<V>;
2126

2227
/**
2328
* \brief Fixed sized cache that can be used with different policy types (e.g. LRU, FIFO, LFU)
2429
* \tparam Key Type of a key (should be hashable)
2530
* \tparam Value Type of a value stored in the cache
2631
* \tparam Policy Type of a policy to be used with the cache
27-
* \tparam HashMap Type of a hashmap to use for cache operations. Should have `std::unordered_map` compatible interface
32+
* \tparam HashMap Type of a hashmap to use for cache operations. Should have `std::unordered_map`
33+
* compatible interface
2834
*/
2935
template <typename Key, typename Value, template <typename> class Policy = NoCachePolicy,
30-
typename HashMap = std::unordered_map<Key, Value>>
36+
typename HashMap = std::unordered_map<Key, WrappedValue<Value>>>
3137
class fixed_sized_cache
3238
{
3339
public:
34-
using iterator = typename HashMap::iterator;
35-
using const_iterator = typename HashMap::const_iterator;
40+
using map_type = HashMap;
41+
using value_type = typename map_type::mapped_type;
42+
using iterator = typename map_type::iterator;
43+
using const_iterator = typename map_type::const_iterator;
3644
using operation_guard = typename std::lock_guard<std::mutex>;
37-
using on_erase_cb = typename std::function<void(const Key &key, const Value &value)>;
45+
using on_erase_cb =
46+
typename std::function<void(const Key &key, const value_type &value)>;
3847

3948
/**
4049
* \brief Fixed sized cache constructor
@@ -45,7 +54,7 @@ class fixed_sized_cache
4554
*/
4655
explicit fixed_sized_cache(
4756
size_t max_size, const Policy<Key> policy = Policy<Key>{},
48-
on_erase_cb on_erase = [](const Key &, const Value &) {})
57+
on_erase_cb on_erase = [](const Key &, const value_type &) {})
4958
: cache_policy{policy}, max_cache_size{max_size}, on_erase_callback{on_erase}
5059
{
5160
if (max_cache_size == 0)
@@ -96,10 +105,13 @@ class fixed_sized_cache
96105
* the element is not presented in the cache. If pair's boolean value is true,
97106
* returned iterator can be used to get access to the element
98107
*/
99-
std::pair<const_iterator, bool> TryGet(const Key &key) const noexcept
108+
std::pair<value_type, bool> TryGet(const Key &key) const noexcept
100109
{
101110
operation_guard lock{safe_op};
102-
return GetInternal(key);
111+
const auto result = GetInternal(key);
112+
113+
return std::make_pair(result.second ? result.first->second : nullptr,
114+
result.second);
103115
}
104116

105117
/**
@@ -110,7 +122,7 @@ class fixed_sized_cache
110122
* \param[in] key Get element by key
111123
* \return Reference to the value stored by the specified key in the cache
112124
*/
113-
const Value &Get(const Key &key) const
125+
value_type Get(const Key &key) const
114126
{
115127
operation_guard lock{safe_op};
116128
auto elem = GetInternal(key);
@@ -176,7 +188,8 @@ class fixed_sized_cache
176188
operation_guard lock{safe_op};
177189

178190
std::for_each(begin(), end(),
179-
[&](const std::pair<const Key, Value> &el) { cache_policy.Erase(el.first); });
191+
[&](const std::pair<const Key, value_type> &el)
192+
{ cache_policy.Erase(el.first); });
180193
cache_items_map.clear();
181194
}
182195

@@ -194,7 +207,7 @@ class fixed_sized_cache
194207
void Insert(const Key &key, const Value &value)
195208
{
196209
cache_policy.Insert(key);
197-
cache_items_map.emplace(std::make_pair(key, value));
210+
cache_items_map.emplace(std::make_pair(key, std::make_shared<Value>(value)));
198211
}
199212

200213
void Erase(const_iterator elem)
@@ -214,7 +227,7 @@ class fixed_sized_cache
214227
void Update(const Key &key, const Value &value)
215228
{
216229
cache_policy.Touch(key);
217-
cache_items_map[key] = value;
230+
cache_items_map[key] = std::make_shared<Value>(value);
218231
}
219232

220233
const_iterator FindElem(const Key &key) const
@@ -236,7 +249,7 @@ class fixed_sized_cache
236249
}
237250

238251
private:
239-
HashMap cache_items_map;
252+
map_type cache_items_map;
240253
mutable Policy<Key> cache_policy;
241254
mutable std::mutex safe_op;
242255
std::size_t max_cache_size;

test/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
macro(add_cache_test _TEST_NAME)
22
add_executable(${_TEST_NAME}_tests
33
${_TEST_NAME}_tests.cpp)
4+
add_coverage_flags(${_TEST_NAME}_tests)
45
target_link_libraries(${_TEST_NAME}_tests
56
${GTEST_MAIN_LIBRARIES} caches)
67
target_include_directories(${_TEST_NAME}_tests PRIVATE ${GTEST_INCLUDE_DIRS} ${parallel-hashmap_SOURCE_DIR})

test/fifo_cache_tests.cpp

Lines changed: 54 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,9 @@ template <typename Key, typename Value>
1111
using fifo_cache_t = typename caches::fixed_sized_cache<Key, Value, caches::FIFOCachePolicy>;
1212
#else
1313
template <typename Key, typename Value>
14-
using fifo_cache_t = typename caches::fixed_sized_cache<Key, Value, caches::FIFOCachePolicy,
15-
phmap::node_hash_map<Key, Value>>;
14+
using fifo_cache_t =
15+
typename caches::fixed_sized_cache<Key, Value, caches::FIFOCachePolicy,
16+
phmap::node_hash_map<Key, std::shared_ptr<Value>>>;
1617
#endif /* CUSTOM_HASHMAP */
1718

1819
TEST(FIFOCache, Simple_Test)
@@ -23,17 +24,17 @@ TEST(FIFOCache, Simple_Test)
2324
fc.Put(2, 20);
2425

2526
EXPECT_EQ(fc.Size(), 2);
26-
EXPECT_EQ(fc.Get(1), 10);
27-
EXPECT_EQ(fc.Get(2), 20);
27+
EXPECT_EQ(*fc.Get(1), 10);
28+
EXPECT_EQ(*fc.Get(2), 20);
2829

2930
fc.Put(1, 30);
3031
EXPECT_EQ(fc.Size(), 2);
31-
EXPECT_EQ(fc.Get(1), 30);
32+
EXPECT_EQ(*fc.Get(1), 30);
3233

3334
fc.Put(3, 30);
3435
EXPECT_THROW(fc.Get(1), std::range_error);
35-
EXPECT_EQ(fc.Get(2), 20);
36-
EXPECT_EQ(fc.Get(3), 30);
36+
EXPECT_EQ(*fc.Get(2), 20);
37+
EXPECT_EQ(*fc.Get(3), 30);
3738
}
3839

3940
TEST(FIFOCache, Missing_Value)
@@ -43,7 +44,7 @@ TEST(FIFOCache, Missing_Value)
4344
fc.Put(1, 10);
4445

4546
EXPECT_EQ(fc.Size(), 1);
46-
EXPECT_EQ(fc.Get(1), 10);
47+
EXPECT_EQ(*fc.Get(1), 10);
4748
EXPECT_THROW(fc.Get(2), std::range_error);
4849
}
4950

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

6263
for (size_t i = 0; i < TEST_SIZE; ++i)
6364
{
64-
EXPECT_EQ(fc.Get(std::to_string('0' + i)), i);
65+
EXPECT_EQ(*fc.Get(std::to_string('0' + i)), i);
6566
}
6667

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

8081
for (size_t i = 0; i < TEST_SIZE / 2; ++i)
8182
{
82-
EXPECT_EQ(fc.Get(std::to_string('a' + i)), i);
83+
EXPECT_EQ(*fc.Get(std::to_string('a' + i)), i);
8384
}
8485

8586
for (size_t i = TEST_SIZE / 2; i < TEST_SIZE; ++i)
8687
{
87-
EXPECT_EQ(fc.Get(std::to_string('0' + i)), i);
88+
EXPECT_EQ(*fc.Get(std::to_string('0' + i)), i);
8889
}
8990
}
9091

@@ -127,7 +128,7 @@ TEST(FIFOCache, TryGet)
127128
{
128129
auto element = cache.TryGet(std::to_string(i));
129130
EXPECT_TRUE(element.second);
130-
EXPECT_EQ(element.first->second, i);
131+
EXPECT_EQ(*element.first, i);
131132
}
132133

133134
for (std::size_t i = TEST_CASE; i < TEST_CASE * 2; ++i)
@@ -136,3 +137,44 @@ TEST(FIFOCache, TryGet)
136137
EXPECT_FALSE(element.second);
137138
}
138139
}
140+
141+
TEST(FIFOCache, GetWithReplacement)
142+
{
143+
fifo_cache_t<std::string, std::size_t> cache{2};
144+
145+
cache.Put("1", 1);
146+
cache.Put("2", 2);
147+
148+
auto element1 = cache.Get("1");
149+
auto element2 = cache.Get("2");
150+
EXPECT_EQ(*element1, 1);
151+
EXPECT_EQ(*element2, 2);
152+
cache.Put("3", 3);
153+
auto element3 = cache.Get("3");
154+
EXPECT_EQ(*element3, 3);
155+
156+
std::string replaced_key;
157+
158+
for (size_t i = 1; i <= 2; ++i)
159+
{
160+
const auto key = std::to_string(i);
161+
162+
if (!cache.Cached(key))
163+
{
164+
replaced_key = key;
165+
}
166+
}
167+
168+
EXPECT_FALSE(cache.Cached(replaced_key));
169+
EXPECT_FALSE(cache.TryGet(replaced_key).second);
170+
EXPECT_THROW(cache.Get(replaced_key), std::range_error);
171+
EXPECT_EQ(*element1, 1);
172+
EXPECT_EQ(*element2, 2);
173+
EXPECT_EQ(*element3, 3);
174+
}
175+
176+
TEST(FIFOCache, InvalidSize)
177+
{
178+
using test_type = fifo_cache_t<std::string, int>;
179+
EXPECT_THROW(test_type cache{0}, std::invalid_argument);
180+
}

0 commit comments

Comments
 (0)