Skip to content

Commit cbded5e

Browse files
Treehugger RobotAndroid (Google) Code Review
authored andcommitted
Merge "Introduce ftl::Finalizer{<F>,Std,Ftl,Ftl1,Ftl2,Ftl3}" into main
2 parents 5ce9acd + b7ce156 commit cbded5e

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)