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