userver: userver/utils/fast_pimpl.hpp Source File
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Modules Pages Concepts
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>()))) : FastPimpl(std::move(*v)) {}
51
52 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-member-init)
53 FastPimpl(const FastPimpl& v) noexcept(noexcept(T(std::declval<const T&>()))) : FastPimpl(*v) {}
54
55 // NOLINTNEXTLINE(bugprone-unhandled-self-assignment,cert-oop54-cpp)
56 FastPimpl& operator=(const FastPimpl& rhs) noexcept(noexcept(std::declval<T&>() = std::declval<const T&>())) {
57 *AsHeld() = *rhs;
58 return *this;
59 }
60
61 FastPimpl& operator=(FastPimpl&& rhs) noexcept(
62 // NOLINTNEXTLINE(performance-noexcept-move-constructor)
63 noexcept(std::declval<T&>() = std::declval<T>())
64 ) {
65 *AsHeld() = std::move(*rhs);
66 return *this;
67 }
68
69 template <typename... Args>
70 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-member-init)
71 explicit FastPimpl(Args&&... args) noexcept(noexcept(T(std::declval<Args>()...))) {
72 ::new (AsHeld()) T(std::forward<Args>(args)...);
73 }
74
75 T* operator->() noexcept { return AsHeld(); }
76
77 const T* operator->() const noexcept { return AsHeld(); }
78
79 T& operator*() noexcept { return *AsHeld(); }
80
81 const T& operator*() const noexcept { return *AsHeld(); }
82
83 ~FastPimpl() noexcept {
84 Validate<sizeof(T), alignof(T), noexcept(std::declval<T*>()->~T())>();
85 std::destroy_at(AsHeld());
86 }
87
88private:
89 // Use a template to make actual sizes visible in the compiler error message.
90 template <std::size_t ActualSize, std::size_t ActualAlignment, bool ActualNoexcept>
91 static void Validate() noexcept {
92 static_assert(Size >= ActualSize, "invalid Size: Size >= sizeof(T) failed");
93 static_assert(!Strict || Size == ActualSize, "invalid Size: Size == sizeof(T) failed");
94
95 static_assert(Alignment % ActualAlignment == 0, "invalid Alignment: Alignment % alignof(T) == 0 failed");
96 static_assert(!Strict || Alignment == ActualAlignment, "invalid Alignment: Alignment == alignof(T) failed");
97
98 static_assert(ActualNoexcept, "Destructor of FastPimpl is marked as noexcept, the ~T() is not");
99 }
100
101 alignas(Alignment) std::byte storage_[Size];
102
103 T* AsHeld() noexcept { return reinterpret_cast<T*>(&storage_); }
104
105 const T* AsHeld() const noexcept { return reinterpret_cast<const T*>(&storage_); }
106};
107
108} // namespace utils
109
110USERVER_NAMESPACE_END