userver
C++ Async Framework
Toggle main menu visibility
Loading...
Searching...
No Matches
lazy_shared_ptr.hpp
Go to the documentation of this file.
1
#
pragma
once
2
3
/// @file userver/utils/lazy_shared_ptr.hpp
4
/// @brief @copybrief utils::LazySharedPtr
5
6
#
include
<
atomic
>
7
#
include
<
functional
>
8
#
include
<
memory
>
9
#
include
<
utility
>
10
11
#
include
<
userver
/
compiler
/
impl
/
lifetime
.
hpp
>
12
#
include
<
userver
/
engine
/
sleep
.
hpp
>
13
#
include
<
userver
/
utils
/
assert
.
hpp
>
14
#
include
<
userver
/
utils
/
shared_readable_ptr
.
hpp
>
15
16
USERVER_NAMESPACE_BEGIN
17
18
namespace
utils
{
19
20
/// @ingroup userver_containers
21
///
22
/// @brief A lazy wrapper around utils::SharedReadablePtr that fetches the data
23
/// on first access.
24
///
25
/// Provides standard thread-safety guarantee: `const` methods can be called
26
/// concurrently. Once fetched, the same data is guaranteed to be returned
27
/// unless the pointer is assigned to.
28
template
<
typename
T>
29
class
LazySharedPtr final {
30
public
:
31
/// @brief The default constructor, initializes with `nullptr`.
32
LazySharedPtr
()
noexcept
: value_(
nullptr
), shared_filled_(
true
), get_data_() {}
33
34
/// @brief The non-lazy constructor
35
LazySharedPtr
(
utils
::SharedReadablePtr<T> ptr)
noexcept
36
: value_(ptr.Get()), shared_filled_(
true
), shared_(std::move(ptr)) {}
37
38
/// @brief The lazy constructor
39
LazySharedPtr
(std::function<
utils
::SharedReadablePtr<T>()> function)
noexcept
: get_data_(std::move(function)) {}
40
41
/// @brief The copy constructor.
42
/// @note If `other` has not been fetched yet, `this` will not fetch
43
/// immediately, so `this` and `other` may end up pointing to different
44
/// objects.
45
LazySharedPtr
(
const
LazySharedPtr& other)
46
: get_data_(other.get_data_)
47
{
48
if
(other.shared_filled_.load()) {
49
value_.store(other.shared_.Get());
50
shared_ = other.shared_;
51
shared_filled_.store(
true
);
52
}
53
}
54
55
/// @brief The move constructor.
56
LazySharedPtr
(LazySharedPtr&& other)
noexcept
57
: value_(other.value_.load()),
58
shared_filled_(other.shared_filled_.load()),
59
shared_(std::move(other.shared_)),
60
get_data_(std::move(other.get_data_)) {}
61
62
/// @brief The copy assignment operator.
63
/// @note Like copy-constructor, it does not generate pointers if `other` do
64
/// not generate them.
65
LazySharedPtr&
operator
=(
const
LazySharedPtr& other) {
66
*
this
= LazySharedPtr(other);
67
return
*
this
;
68
}
69
70
/// @brief The move assignment operator.
71
LazySharedPtr&
operator
=(LazySharedPtr&& other)
noexcept
{
72
value_.store(other.value_.load(), std::memory_order_relaxed);
73
shared_filled_.store(other.shared_filled_.load(), std::memory_order_relaxed);
74
shared_ = std::move(other.shared_);
75
get_data_ = std::move(other.get_data_);
76
}
77
78
/// @brief Get a pointer to the data (may be null). Fetches the data on the
79
/// first access.
80
const
T*
Get
()
const
& USERVER_IMPL_LIFETIME_BOUND {
81
if
(
const
auto
* current_value = value_.load(); current_value != kUnfilled) {
82
return
current_value;
83
}
84
auto
readable = get_data_();
85
if
(
const
auto
* expected = kUnfilled; value_.compare_exchange_strong(expected, readable.Get())) {
86
shared_ = std::move(readable);
87
shared_filled_.store(
true
);
88
}
89
return
value_;
90
}
91
92
/// @brief Get a smart pointer to the data (may be null). Fetches the data on
93
/// the first access.
94
const
utils
::SharedReadablePtr<T>&
GetShared
()
const
& USERVER_IMPL_LIFETIME_BOUND {
95
if
(value_ == kUnfilled) {
96
auto
readable = get_data_();
97
if
(
const
auto
* expected = kUnfilled; value_.compare_exchange_strong(expected, readable.Get())) {
98
shared_ = std::move(readable);
99
shared_filled_.store(
true
);
100
}
101
}
102
while
(!shared_filled_.load()) {
103
// Another thread has filled 'value_', but not yet 'shared_'. It will be
104
// filled in a few CPU cycles. Discovering LazySharedPtr in such a state
105
// is expected to be rare.
106
engine
::
Yield
(
)
;
107
}
108
return
shared_;
109
}
110
111
///@returns `*Get()`
112
///@note `Get()` must not be `nullptr`.
113
const
T&
operator
*()
const
& USERVER_IMPL_LIFETIME_BOUND {
114
const
auto
* res =
Get
(
)
;
115
UASSERT
(res);
116
return
*res;
117
}
118
119
/// @returns `Get()`
120
/// @note `Get()` must not be `nullptr`.
121
const
T*
operator
->()
const
& USERVER_IMPL_LIFETIME_BOUND {
return
&**
this
; }
122
123
/// @returns `Get() != nullptr`
124
explicit
operator
bool
()
const
& USERVER_IMPL_LIFETIME_BOUND {
return
!!
Get
(
)
; }
125
126
/// @returns `GetShared()`
127
operator
const
utils
::SharedReadablePtr<T>&()
const
& USERVER_IMPL_LIFETIME_BOUND {
return
GetShared
(
)
; }
128
129
/// @returns `GetShared()`
130
operator
std::shared_ptr<
const
T>()
const
& USERVER_IMPL_LIFETIME_BOUND {
return
GetShared
(
)
; }
131
132
private
:
133
static
inline
const
auto
kUnfilled =
reinterpret_cast
<
const
T*>(std::uintptr_t{1});
134
135
mutable
std::atomic<
const
T*> value_{kUnfilled};
136
mutable
std::atomic<
bool
> shared_filled_{
false
};
137
mutable
utils
::SharedReadablePtr<T> shared_{
nullptr
};
138
std::function<
utils
::SharedReadablePtr<T>()> get_data_{};
139
};
140
141
/// @brief Make a lazy pointer to the data of a cache.
142
///
143
/// The cache type must have:
144
/// - `DataType` member type;
145
/// - `Get() -> utils::SharedReadablePtr<DataType>` method.
146
///
147
/// For example, components::CachingComponentBase satisfies these requirements.
148
template
<
typename
Cache>
149
LazySharedPtr<
typename
Cache::
DataType
>
MakeLazyCachePtr
(Cache& cache) {
150
return
utils
::LazySharedPtr<
typename
Cache::DataType>([&cache] {
return
cache.Get(); });
151
}
152
153
}
// namespace utils
154
155
USERVER_NAMESPACE_END
userver
utils
lazy_shared_ptr.hpp
Generated on
for userver by
Doxygen
1.17.0