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