1 /* 2 * Copyright (c) Facebook, Inc. and its affiliates. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 #pragma once 18 19 #include <initializer_list> 20 #include <new> 21 #include <stdexcept> 22 #include <type_traits> 23 24 #include <folly/lang/SafeAssert.h> 25 #include <folly/synchronization/CallOnce.h> 26 27 namespace folly { 28 29 /** 30 * DelayedInit -- thread-safe delayed initialization of a value. There are two 31 * important differences between Lazy and DelayedInit: 32 * 1. DelayedInit does not store the factory function inline. 33 * 2. DelayedInit is thread-safe. 34 * 35 * Due to these differences, DelayedInit is suitable for data members. Lazy is 36 * best for local stack variables. 37 * 38 * Example Usage: 39 * 40 * struct Foo { 41 * Bar& bar() { 42 * LargeState state; 43 * return bar_.try_emplace_with( 44 * [this, &state] { return computeBar(state); }); 45 * } 46 * private: 47 * Bar computeBar(LargeState&); 48 * DelayedInit<Bar> bar_; 49 * }; 50 * 51 * If the above example were to use Lazy instead of DelayedInit: 52 * - Storage for LargeState and this-pointer would need to be reserved in the 53 * struct which wastes memory. 54 * - It would require additional synchronization logic for thread-safety. 55 * 56 * 57 * Rationale: 58 * 59 * - The stored value is initialized at most once and never deinitialized. 60 * Unlike Lazy, the initialization logic must be provided by the consumer. 61 * This means that DelayedInit is more of a "storage" type like 62 * std::optional. These semantics are perfect for thread-safe, lazy 63 * initialization of a data member. 64 * 65 * - DelayedInit models neither MoveConstructible nor CopyConstructible. The 66 * rationale is the same as that of std::once_flag. 67 * 68 * - There is no need for a non-thread-safe version of DelayedInit. 69 * std::optional will suffice in these cases. 70 */ 71 template <typename T> 72 struct DelayedInit { 73 DelayedInit() = default; 74 DelayedInit(const DelayedInit&) = delete; 75 DelayedInit& operator=(const DelayedInit&) = delete; 76 77 /** 78 * Gets the pre-existing value if already initialized or creates the value 79 * returned by the provided factory function. If the value already exists, 80 * then the provided function is not called. 81 */ 82 template <typename Func> try_emplace_withDelayedInit83 T& try_emplace_with(Func func) { 84 auto addr = static_cast<void*>(std::addressof(storage_.value)); 85 call_once(storage_.init, [&] { ::new (addr) T(func()); }); 86 return storage_.value; 87 } 88 89 /** 90 * Gets the pre-existing value if already initialized or constructs the value 91 * in-place by direct-initializing with the provided arguments. 92 */ 93 template <typename... A> try_emplaceDelayedInit94 T& try_emplace(A&&... a) { 95 return try_emplace_with([&] { return T(static_cast<A&&>(a)...); }); 96 } 97 template < 98 typename U, 99 typename... A, 100 typename = std::enable_if_t< 101 std::is_constructible<T, std::initializer_list<U>, A...>::value>> try_emplaceDelayedInit102 T& try_emplace(std::initializer_list<U> ilist, A&&... a) { 103 return try_emplace_with([&] { return T(ilist, static_cast<A&&>(a)...); }); 104 } 105 has_valueDelayedInit106 bool has_value() const { return test_once(storage_.init); } 107 explicit operator bool() const { return has_value(); } 108 valueDelayedInit109 T& value() { 110 require_value(); 111 return storage_.value; 112 } 113 valueDelayedInit114 const T& value() const { 115 require_value(); 116 return storage_.value; 117 } 118 119 T& operator*() { 120 FOLLY_SAFE_DCHECK(has_value(), "tried to access empty DelayedInit"); 121 return storage_.value; 122 } 123 const T& operator*() const { 124 FOLLY_SAFE_DCHECK(has_value(), "tried to access empty DelayedInit"); 125 return storage_.value; 126 } 127 T* operator->() { 128 FOLLY_SAFE_DCHECK(has_value(), "tried to access empty DelayedInit"); 129 return std::addressof(storage_.value); 130 } 131 const T* operator->() const { 132 FOLLY_SAFE_DCHECK(has_value(), "tried to access empty DelayedInit"); 133 return std::addressof(storage_.value); 134 } 135 136 private: require_valueDelayedInit137 void require_value() const { 138 if (!has_value()) { 139 throw_exception<std::logic_error>("tried to access empty DelayedInit"); 140 } 141 } 142 143 using OnceFlag = std::conditional_t< 144 alignof(T) >= sizeof(once_flag), 145 once_flag, 146 compact_once_flag>; 147 148 struct StorageTriviallyDestructible { 149 union { 150 std::remove_const_t<T> value; 151 }; 152 OnceFlag init; 153 StorageTriviallyDestructibleDelayedInit::StorageTriviallyDestructible154 StorageTriviallyDestructible() {} 155 }; 156 157 struct StorageNonTriviallyDestructible { 158 union { 159 std::remove_const_t<T> value; 160 }; 161 OnceFlag init; 162 StorageNonTriviallyDestructibleDelayedInit::StorageNonTriviallyDestructible163 StorageNonTriviallyDestructible() {} ~StorageNonTriviallyDestructibleDelayedInit::StorageNonTriviallyDestructible164 ~StorageNonTriviallyDestructible() { 165 if (test_once(this->init)) { 166 this->value.~T(); 167 } 168 } 169 }; 170 171 using Storage = std::conditional_t< 172 std::is_trivially_destructible<T>::value, 173 StorageTriviallyDestructible, 174 StorageNonTriviallyDestructible>; 175 176 Storage storage_; 177 }; 178 179 } // namespace folly 180