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