blob: 0251957ad093432f2bf4d0ce41a685b97d23da69 [file] [log] [blame]
Lloyd Piqueb7ce1562024-10-30 12:47:43 -07001/*
2 * Copyright 2024 The Android Open Source Project
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 <cstddef>
20
21#include <functional>
22#include <type_traits>
23#include <utility>
24
25#include <ftl/function.h>
26
27namespace android::ftl {
28
29// An RAII wrapper that invokes a function object as a finalizer when destroyed.
30//
31// The function object must take no arguments, and must return void. If the function object needs
32// any context for the call, it must store it itself, for example with a lambda capture.
33//
34// The stored function object will be called once (unless canceled via the `cancel()` member
35// function) at the first of:
36//
37// - The Finalizer instance is destroyed.
38// - `operator()` is used to invoke the contained function.
39// - The Finalizer instance is move-assigned a new value. The function being replaced will be
40// invoked, and the replacement will be stored to be called later.
41//
42// The intent with this class is to keep cleanup code next to the code that requires that
43// cleanup be performed.
44//
45// bool read_file(std::string filename) {
46// FILE* f = fopen(filename.c_str(), "rb");
47// if (f == nullptr) return false;
48// const auto cleanup = ftl::Finalizer([f]() { fclose(f); });
49// // fread(...), etc
50// return true;
51// }
52//
53// The `FinalFunction` template argument to Finalizer<FinalFunction> allows a polymorphic function
54// type for storing the finalization function, such as `std::function` or `ftl::Function`.
55//
56// For convenience, this header defines a few useful aliases for using those types.
57//
58// - `FinalizerStd`, an alias for `Finalizer<std::function<void()>>`
59// - `FinalizerFtl`, an alias for `Finalizer<ftl::Function<void()>>`
60// - `FinalizerFtl1`, an alias for `Finalizer<ftl::Function<void(), 1>>`
61// - `FinalizerFtl2`, an alias for `Finalizer<ftl::Function<void(), 2>>`
62// - `FinalizerFtl3`, an alias for `Finalizer<ftl::Function<void(), 3>>`
63//
64// Clients of this header are free to define other aliases they need.
65//
66// A Finalizer that uses a polymorphic function type can be returned from a function call and/or
67// stored as member data (to be destroyed along with the containing class).
68//
69// auto register(Observer* observer) -> ftl::FinalizerStd<void()> {
70// const auto id = observers.add(observer);
71// return ftl::Finalizer([id]() { observers.remove(id); });
72// }
73//
74// {
75// const auto _ = register(observer);
76// // do the things that required the registered observer.
77// }
78// // the observer is removed.
79//
80// Cautions:
81//
82// 1. When a Finalizer is stored as member data, you will almost certainly want that cleanup to
83// happen first, before the rest of the other member data is destroyed. For safety you should
84// assume that the finalization function will access that data directly or indirectly.
85//
86// This means that Finalizers should be defined last, after all other normal member data in a
87// class.
88//
89// class MyClass {
90// public:
91// bool initialize() {
92// ready_ = true;
93// cleanup_ = ftl::Finalizer([this]() { ready_ = false; });
94// return true;
95// }
96//
97// bool ready_ = false;
98//
99// // Finalizers should be last so other class members can be accessed before being
100// // destroyed.
101// ftl::FinalizerStd<void()> cleanup_;
102// };
103//
104// 2. Care must be taken to use `ftl::Finalizer()` when constructing locally from a lambda. If you
105// forget to do so, you are just creating a lambda that won't be automatically invoked!
106//
107// const auto bad = [&counter](){ ++counter; }; // Just a lambda instance
108// const auto good = ftl::Finalizer([&counter](){ ++counter; });
109//
110template <typename FinalFunction>
111class Finalizer final {
112 // requires(std::is_invocable_r_v<void, FinalFunction>)
113 static_assert(std::is_invocable_r_v<void, FinalFunction>);
114
115 public:
116 // A default constructed Finalizer does nothing when destroyed.
117 // requires(std::is_default_constructible_v<FinalFunction>)
118 constexpr Finalizer() = default;
119
120 // Constructs a Finalizer from a function object.
121 // requires(std::is_invocable_v<F>)
122 template <typename F, typename = std::enable_if_t<std::is_invocable_v<F>>>
123 [[nodiscard]] explicit constexpr Finalizer(F&& function)
124 : Finalizer(std::forward<F>(function), false) {}
125
126 constexpr ~Finalizer() { maybe_invoke(); }
127
128 // Disallow copying.
129 Finalizer(const Finalizer& that) = delete;
130 auto operator=(const Finalizer& that) = delete;
131
132 // Move construction
133 // requires(std::is_move_constructible_v<FinalFunction>)
134 [[nodiscard]] constexpr Finalizer(Finalizer&& that)
135 : Finalizer(std::move(that.function_), std::exchange(that.canceled_, true)) {}
136
137 // Implicit conversion move construction
138 // requires(!std::is_same_v<Finalizer, Finalizer<F>>)
139 template <typename F, typename = std::enable_if_t<!std::is_same_v<Finalizer, Finalizer<F>>>>
140 // NOLINTNEXTLINE(google-explicit-constructor, cppcoreguidelines-rvalue-reference-param-not-moved)
141 [[nodiscard]] constexpr Finalizer(Finalizer<F>&& that)
142 : Finalizer(std::move(that.function_), std::exchange(that.canceled_, true)) {}
143
144 // Move assignment
145 // requires(std::is_move_assignable_v<FinalFunction>)
146 constexpr auto operator=(Finalizer&& that) -> Finalizer& {
147 maybe_invoke();
148
149 function_ = std::move(that.function_);
150 canceled_ = std::exchange(that.canceled_, true);
151
152 return *this;
153 }
154
155 // Implicit conversion move assignment
156 // requires(!std::is_same_v<Finalizer, Finalizer<F>>)
157 template <typename F, typename = std::enable_if_t<!std::is_same_v<Finalizer, Finalizer<F>>>>
158 // NOLINTNEXTLINE(cppcoreguidelines-rvalue-reference-param-not-moved)
159 constexpr auto operator=(Finalizer<F>&& that) -> Finalizer& {
160 *this = Finalizer(std::move(that.function_), std::exchange(that.canceled_, true));
161 return *this;
162 }
163
164 // Cancels the final function, preventing it from being invoked.
165 constexpr void cancel() {
166 canceled_ = true;
167 maybe_nullify_function();
168 }
169
170 // Invokes the final function now, if not already invoked.
171 constexpr void operator()() { maybe_invoke(); }
172
173 private:
174 template <typename>
175 friend class Finalizer;
176
177 template <typename F, typename = std::enable_if_t<std::is_invocable_v<F>>>
178 [[nodiscard]] explicit constexpr Finalizer(F&& function, bool canceled)
179 : function_(std::forward<F>(function)), canceled_(canceled) {}
180
181 constexpr void maybe_invoke() {
182 if (!std::exchange(canceled_, true)) {
183 std::invoke(function_);
184 maybe_nullify_function();
185 }
186 }
187
188 constexpr void maybe_nullify_function() {
189 // Sets function_ to nullptr if that is supported for the backing type.
190 if constexpr (std::is_assignable_v<FinalFunction, nullptr_t>) {
191 function_ = nullptr;
192 }
193 }
194
195 FinalFunction function_;
196 bool canceled_ = true;
197};
198
199template <typename F>
200Finalizer(F&&) -> Finalizer<std::decay_t<F>>;
201
202// A standard alias for using `std::function` as the polymorphic function type.
203using FinalizerStd = Finalizer<std::function<void()>>;
204
205// Helpful aliases for using `ftl::Function` as the polymorphic function type.
206using FinalizerFtl = Finalizer<Function<void()>>;
207using FinalizerFtl1 = Finalizer<Function<void(), 1>>;
208using FinalizerFtl2 = Finalizer<Function<void(), 2>>;
209using FinalizerFtl3 = Finalizer<Function<void(), 3>>;
210
211} // namespace android::ftl