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