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