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