• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
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 
27 namespace 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 //
110 template <typename FinalFunction>
111 class 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>>>
Finalizer(F && function)123   [[nodiscard]] explicit constexpr Finalizer(F&& function)
124       : Finalizer(std::forward<F>(function), false) {}
125 
~Finalizer()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>)
Finalizer(Finalizer && that)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)
Finalizer(Finalizer<F> && that)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.
cancel()165   constexpr void cancel() {
166     canceled_ = true;
167     maybe_nullify_function();
168   }
169 
170   // Invokes the final function now, if not already invoked.
operator()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>>>
Finalizer(F && function,bool canceled)178   [[nodiscard]] explicit constexpr Finalizer(F&& function, bool canceled)
179       : function_(std::forward<F>(function)), canceled_(canceled) {}
180 
maybe_invoke()181   constexpr void maybe_invoke() {
182     if (!std::exchange(canceled_, true)) {
183       std::invoke(function_);
184       maybe_nullify_function();
185     }
186   }
187 
maybe_nullify_function()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 
199 template <typename F>
200 Finalizer(F&&) -> Finalizer<std::decay_t<F>>;
201 
202 // A standard alias for using `std::function` as the polymorphic function type.
203 using FinalizerStd = Finalizer<std::function<void()>>;
204 
205 // Helpful aliases for using `ftl::Function` as the polymorphic function type.
206 using FinalizerFtl = Finalizer<Function<void()>>;
207 using FinalizerFtl1 = Finalizer<Function<void(), 1>>;
208 using FinalizerFtl2 = Finalizer<Function<void(), 2>>;
209 using FinalizerFtl3 = Finalizer<Function<void(), 3>>;
210 
211 }  // namespace android::ftl