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 <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