userver: userver/utils/shared_readable_ptr.hpp Source File
Loading...
Searching...
No Matches
shared_readable_ptr.hpp
Go to the documentation of this file.
1#pragma once
2
3/// @file userver/utils/shared_readable_ptr.hpp
4/// @brief @copybrief utils::SharedReadablePtr
5
6#include <memory>
7#include <type_traits>
8
9#include <userver/compiler/impl/lifetime.hpp>
10
11USERVER_NAMESPACE_BEGIN
12
13namespace utils {
14
15/// @ingroup userver_universal userver_containers
16///
17/// @brief `std::shared_ptr<const T>` wrapper that makes sure that the pointer
18/// is stored before dereferencing. Protects from dangling references.
19///
20/// Example of dangling:
21/// @code
22/// // BAD! Result of `cache.Get()` may be destroyed after the invocation.
23/// const auto& snapshot = *cache.Get();
24/// Use(snapshot);
25/// @endcode
26///
27/// Such code is may work fine 99.9% of that time and, and such bugs are not detectable by most tests.
28/// This is because typically the cache data will be held by the cache itself longer than the runtime
29/// of the current handler (or whatever) that uses the data.
30/// However, 0.1% of the time there will be a crash, because at that exact time the cache will update itself, replacing
31/// the data snapshot and dropping the old shared pointer, which will turn out to be the only one.
32///
33/// The correct way to handle shared pointers:
34/// @code
35/// // Stores the shared pointer.
36/// const auto snapshot = cache.Get();
37/// // We only have the right to use `*snapshot` while we hold `snapshot` itself.
38/// Use(*snapshot);
39/// @endcode
40template <typename T>
41class SharedReadablePtr final {
42 static_assert(!std::is_const_v<T>, "SharedReadablePtr already adds `const` to `T`");
43 static_assert(!std::is_reference_v<T>, "SharedReadablePtr does not work with references");
44
45public:
46 using Base = std::shared_ptr<const T>;
47 using MutableBase = std::shared_ptr<T>;
48 using Weak = typename Base::weak_type;
49 using Unique = std::unique_ptr<const T>;
50 using element_type = T;
51
52 SharedReadablePtr(const SharedReadablePtr& ptr) noexcept = default;
53 SharedReadablePtr(SharedReadablePtr&& ptr) noexcept = default;
54
55 constexpr SharedReadablePtr(std::nullptr_t) noexcept : base_(nullptr) {}
56
57 SharedReadablePtr& operator=(const SharedReadablePtr& ptr) noexcept = default;
58 SharedReadablePtr& operator=(SharedReadablePtr&& ptr) noexcept = default;
59
60 SharedReadablePtr(const Base& ptr) noexcept : base_(ptr) {}
61
62 SharedReadablePtr(Base&& ptr) noexcept : base_(std::move(ptr)) {}
63
64 SharedReadablePtr(const MutableBase& ptr) noexcept : base_(ptr) {}
65
66 SharedReadablePtr(MutableBase&& ptr) noexcept : base_(std::move(ptr)) {}
67
68 SharedReadablePtr(Unique&& ptr) noexcept : base_(std::move(ptr)) {}
69
70 SharedReadablePtr& operator=(const Base& ptr) noexcept {
71 base_ = ptr;
72 return *this;
73 }
74
75 SharedReadablePtr& operator=(Base&& ptr) noexcept {
76 base_ = std::move(ptr);
77 return *this;
78 }
79
80 SharedReadablePtr& operator=(const MutableBase& ptr) noexcept {
81 base_ = ptr;
82 return *this;
83 }
84
85 SharedReadablePtr& operator=(MutableBase&& ptr) noexcept {
86 base_ = std::move(ptr);
87 return *this;
88 }
89
90 SharedReadablePtr& operator=(std::nullptr_t) noexcept {
91 Reset();
92 return *this;
93 }
94
95 const T* Get() const& noexcept USERVER_IMPL_LIFETIME_BOUND { return base_.get(); }
96
97 const T& operator*() const& noexcept USERVER_IMPL_LIFETIME_BOUND { return *base_; }
98
99 const T& operator*() && { ReportMisuse(); }
100
101 const T* operator->() const& noexcept USERVER_IMPL_LIFETIME_BOUND { return base_.get(); }
102
103 const T* operator->() && { ReportMisuse(); }
104
105 operator const Base&() const& noexcept USERVER_IMPL_LIFETIME_BOUND { return base_; }
106
107 operator const Base&() && { ReportMisuse(); }
108
109 operator Weak() const& noexcept USERVER_IMPL_LIFETIME_BOUND { return base_; }
110
111 operator Weak() && { ReportMisuse(); }
112
113 explicit operator bool() const noexcept { return !!base_; }
114
115 bool operator==(const SharedReadablePtr<T>& other) const { return base_ == other.base_; }
116
117 bool operator!=(const SharedReadablePtr<T>& other) const { return !(*this == other); }
118
119 void Reset() noexcept { base_.reset(); }
120
121private:
122 [[noreturn]] static void ReportMisuse() { static_assert(!sizeof(T), "keep the pointer before using, please"); }
123
124 Base base_;
125};
126
127template <typename T>
128bool operator==(const SharedReadablePtr<T>& ptr, std::nullptr_t) {
129 return !ptr;
130}
131
132template <typename T>
133bool operator==(std::nullptr_t, const SharedReadablePtr<T>& ptr) {
134 return !ptr;
135}
136
137template <typename T>
138bool operator!=(const SharedReadablePtr<T>& ptr, std::nullptr_t) {
139 return !(ptr == nullptr);
140}
141
142template <typename T>
143bool operator!=(std::nullptr_t, const SharedReadablePtr<T>& ptr) {
144 return !(nullptr == ptr);
145}
146
147template <typename T, typename... Args>
148SharedReadablePtr<T> MakeSharedReadable(Args&&... args) {
149 return SharedReadablePtr<T>{std::make_shared<T>(std::forward<Args>(args)...)};
150}
151
152} // namespace utils
153
154USERVER_NAMESPACE_END