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