userver: userver/utils/fast_pimpl.hpp Source File
Loading...
Searching...
No Matches
fast_pimpl.hpp
Go to the documentation of this file.
1#pragma once
2
3/// @file userver/utils/fast_pimpl.hpp
4/// @brief @copybrief utils::FastPimpl
5
6#include <cstddef>
7#include <memory>
8#include <new>
9#include <type_traits>
10#include <utility>
11
12#include <userver/compiler/impl/lifetime.hpp>
13#include <userver/compiler/impl/nodebug.hpp>
14
15USERVER_NAMESPACE_BEGIN
16
17namespace utils {
18
19/// @brief Helper constant to use with FastPimpl
20inline constexpr bool kStrictMatch = true;
21
22/// @ingroup userver_universal userver_containers
23///
24/// @brief Implements pimpl idiom without dynamic memory allocation.
25///
26/// FastPimpl doesn't require either memory allocation or indirect memory
27/// access. But you have to manually set object size when you instantiate
28/// FastPimpl.
29///
30/// ## Example usage:
31/// Take your class with pimpl via smart pointer and
32/// replace the smart pointer with utils::FastPimpl<Impl, Size, Alignment>
33/// @snippet utils/widget_fast_pimpl_test.hpp FastPimpl - header
34///
35/// If the Size and Alignment are unknown - just put a random ones and
36/// the compiler would show the right ones in the error message:
37/// @code
38/// In instantiation of 'void FastPimpl<T, Size, Alignment>::Validate()
39/// [with int ActualSize = 1; int ActualAlignment = 8; T = sample::Widget;
40/// int Size = 8; int Alignment = 8]'
41/// @endcode
42///
43/// Change the initialization in source file to not allocate for pimpl
44/// @snippet utils/widget_fast_pimpl_test.cpp FastPimpl - source
45///
46/// Done! Now you can use the header without exposing the implementation
47/// details:
48/// @snippet utils/fast_pimpl_test.cpp FastPimpl - usage
49template <class T, std::size_t Size, std::size_t Alignment, bool Strict = false>
50class FastPimpl final {
51public:
52 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-member-init,performance-noexcept-move-constructor)
53 USERVER_IMPL_NODEBUG_INLINE_FUNC FastPimpl(FastPimpl&& v) noexcept(noexcept(T(std::declval<T>())))
54 : FastPimpl(std::move(*v))
55 {}
56
57 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-member-init)
58 USERVER_IMPL_NODEBUG_INLINE_FUNC FastPimpl(const FastPimpl& v) noexcept(noexcept(T(std::declval<const T&>())))
59 : FastPimpl(*v)
60 {}
61
62 // NOLINTNEXTLINE(bugprone-unhandled-self-assignment,cert-oop54-cpp)
63 USERVER_IMPL_NODEBUG_INLINE_FUNC FastPimpl& operator=(const FastPimpl& rhs
64 ) noexcept(noexcept(std::declval<T&>() = std::declval<const T&>())) {
65 *AsHeld() = *rhs;
66 return *this;
67 }
68
69 USERVER_IMPL_NODEBUG_INLINE_FUNC FastPimpl& operator=(FastPimpl&& rhs) noexcept(
70 // NOLINTNEXTLINE(performance-noexcept-move-constructor)
71 noexcept(std::declval<T&>() = std::declval<T>())
72 ) {
73 *AsHeld() = std::move(*rhs);
74 return *this;
75 }
76
77 template <typename... Args>
78 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-member-init)
79 USERVER_IMPL_NODEBUG_INLINE_FUNC explicit FastPimpl(Args&&... args) noexcept(noexcept(T(std::declval<Args>()...))) {
80 ::new (AsHeld()) T(std::forward<Args>(args)...);
81 }
82
83 USERVER_IMPL_NODEBUG_INLINE_FUNC T* operator->() noexcept USERVER_IMPL_LIFETIME_BOUND { return AsHeld(); }
84
85 USERVER_IMPL_NODEBUG_INLINE_FUNC const T* operator->() const noexcept USERVER_IMPL_LIFETIME_BOUND {
86 return AsHeld();
87 }
88
89 USERVER_IMPL_NODEBUG_INLINE_FUNC T& operator*() noexcept USERVER_IMPL_LIFETIME_BOUND { return *AsHeld(); }
90
91 USERVER_IMPL_NODEBUG_INLINE_FUNC const T& operator*() const noexcept USERVER_IMPL_LIFETIME_BOUND {
92 return *AsHeld();
93 }
94
95 USERVER_IMPL_NODEBUG_INLINE_FUNC ~FastPimpl() noexcept {
96 Validate<sizeof(T), alignof(T), noexcept(std::declval<T*>()->~T())>();
97 std::destroy_at(AsHeld());
98 }
99
100private:
101 // Use a template to make actual sizes visible in the compiler error message.
102 template <std::size_t ActualSize, std::size_t ActualAlignment, bool ActualNoexcept>
103 USERVER_IMPL_NODEBUG_INLINE_FUNC static void Validate() noexcept {
104 static_assert(Size >= ActualSize, "invalid Size: Size >= sizeof(T) failed");
105 static_assert(!Strict || Size == ActualSize, "invalid Size: Size == sizeof(T) failed");
106
107 static_assert(Alignment % ActualAlignment == 0, "invalid Alignment: Alignment % alignof(T) == 0 failed");
108 static_assert(!Strict || Alignment == ActualAlignment, "invalid Alignment: Alignment == alignof(T) failed");
109
110 static_assert(ActualNoexcept, "Destructor of FastPimpl is marked as noexcept, the ~T() is not");
111 }
112
113 alignas(Alignment) std::byte storage_[Size];
114
115 USERVER_IMPL_NODEBUG_INLINE_FUNC T* AsHeld() noexcept { return reinterpret_cast<T*>(&storage_); }
116
117 USERVER_IMPL_NODEBUG_INLINE_FUNC const T* AsHeld() const noexcept { return reinterpret_cast<const T*>(&storage_); }
118};
119
120} // namespace utils
121
122USERVER_NAMESPACE_END