Skip to content

Commit b7ce156

Browse files
committed
Introduce ftl::Finalizer{<F>,Std,Ftl,Ftl1,Ftl2,Ftl3}
A Finalizer ensures a final cleanup function is executed when it is destroyed. The cleanup can also be explicitly invoked before destruction, or canceled so it is not executed. As they are meant for one-time cleanup, Finalizers are moveable but not copyable. The templated `Finalizer<F>` can be constructed from any function object that can be invoked with no arguments and that returns void. If `F` can be default constructed, then `Finalizer<F>` can be default constructed. `ftl::FinalizerStd` is an alias that uses `std::function` as the function object type. `ftl::FinalizerFtl`, `ftl::FinalizerFtl1`, `ftl::FinalizerFtl2` and `ftl::FinalizerFtl3` are aliases that use `ftl::Function` as the type, with increasing amounts of fixed-size internal storage for captured values. These are useful when specifying a return type or a storage type, and are all default-construcible. Bug: 185536303 Flag: EXEMPT New library code Test: atest ftl_test Change-Id: If4a130873ae38727e5bafd634344ae341640e742
1 parent 9bf3e87 commit b7ce156

3 files changed

Lines changed: 421 additions & 0 deletions

File tree

include/ftl/finalizer.h

Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
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>>>
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+
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

libs/ftl/Android.bp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ cc_test {
2121
"enum_test.cpp",
2222
"expected_test.cpp",
2323
"fake_guard_test.cpp",
24+
"finalizer_test.cpp",
2425
"flags_test.cpp",
2526
"function_test.cpp",
2627
"future_test.cpp",

0 commit comments

Comments
 (0)