blob: 0251957ad093432f2bf4d0ce41a685b97d23da69 [file] [log] [blame] [edit]
/*
* Copyright 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <cstddef>
#include <functional>
#include <type_traits>
#include <utility>
#include <ftl/function.h>
namespace android::ftl {
// An RAII wrapper that invokes a function object as a finalizer when destroyed.
//
// The function object must take no arguments, and must return void. If the function object needs
// any context for the call, it must store it itself, for example with a lambda capture.
//
// The stored function object will be called once (unless canceled via the `cancel()` member
// function) at the first of:
//
// - The Finalizer instance is destroyed.
// - `operator()` is used to invoke the contained function.
// - The Finalizer instance is move-assigned a new value. The function being replaced will be
// invoked, and the replacement will be stored to be called later.
//
// The intent with this class is to keep cleanup code next to the code that requires that
// cleanup be performed.
//
// bool read_file(std::string filename) {
// FILE* f = fopen(filename.c_str(), "rb");
// if (f == nullptr) return false;
// const auto cleanup = ftl::Finalizer([f]() { fclose(f); });
// // fread(...), etc
// return true;
// }
//
// The `FinalFunction` template argument to Finalizer<FinalFunction> allows a polymorphic function
// type for storing the finalization function, such as `std::function` or `ftl::Function`.
//
// For convenience, this header defines a few useful aliases for using those types.
//
// - `FinalizerStd`, an alias for `Finalizer<std::function<void()>>`
// - `FinalizerFtl`, an alias for `Finalizer<ftl::Function<void()>>`
// - `FinalizerFtl1`, an alias for `Finalizer<ftl::Function<void(), 1>>`
// - `FinalizerFtl2`, an alias for `Finalizer<ftl::Function<void(), 2>>`
// - `FinalizerFtl3`, an alias for `Finalizer<ftl::Function<void(), 3>>`
//
// Clients of this header are free to define other aliases they need.
//
// A Finalizer that uses a polymorphic function type can be returned from a function call and/or
// stored as member data (to be destroyed along with the containing class).
//
// auto register(Observer* observer) -> ftl::FinalizerStd<void()> {
// const auto id = observers.add(observer);
// return ftl::Finalizer([id]() { observers.remove(id); });
// }
//
// {
// const auto _ = register(observer);
// // do the things that required the registered observer.
// }
// // the observer is removed.
//
// Cautions:
//
// 1. When a Finalizer is stored as member data, you will almost certainly want that cleanup to
// happen first, before the rest of the other member data is destroyed. For safety you should
// assume that the finalization function will access that data directly or indirectly.
//
// This means that Finalizers should be defined last, after all other normal member data in a
// class.
//
// class MyClass {
// public:
// bool initialize() {
// ready_ = true;
// cleanup_ = ftl::Finalizer([this]() { ready_ = false; });
// return true;
// }
//
// bool ready_ = false;
//
// // Finalizers should be last so other class members can be accessed before being
// // destroyed.
// ftl::FinalizerStd<void()> cleanup_;
// };
//
// 2. Care must be taken to use `ftl::Finalizer()` when constructing locally from a lambda. If you
// forget to do so, you are just creating a lambda that won't be automatically invoked!
//
// const auto bad = [&counter](){ ++counter; }; // Just a lambda instance
// const auto good = ftl::Finalizer([&counter](){ ++counter; });
//
template <typename FinalFunction>
class Finalizer final {
// requires(std::is_invocable_r_v<void, FinalFunction>)
static_assert(std::is_invocable_r_v<void, FinalFunction>);
public:
// A default constructed Finalizer does nothing when destroyed.
// requires(std::is_default_constructible_v<FinalFunction>)
constexpr Finalizer() = default;
// Constructs a Finalizer from a function object.
// requires(std::is_invocable_v<F>)
template <typename F, typename = std::enable_if_t<std::is_invocable_v<F>>>
[[nodiscard]] explicit constexpr Finalizer(F&& function)
: Finalizer(std::forward<F>(function), false) {}
constexpr ~Finalizer() { maybe_invoke(); }
// Disallow copying.
Finalizer(const Finalizer& that) = delete;
auto operator=(const Finalizer& that) = delete;
// Move construction
// requires(std::is_move_constructible_v<FinalFunction>)
[[nodiscard]] constexpr Finalizer(Finalizer&& that)
: Finalizer(std::move(that.function_), std::exchange(that.canceled_, true)) {}
// Implicit conversion move construction
// requires(!std::is_same_v<Finalizer, Finalizer<F>>)
template <typename F, typename = std::enable_if_t<!std::is_same_v<Finalizer, Finalizer<F>>>>
// NOLINTNEXTLINE(google-explicit-constructor, cppcoreguidelines-rvalue-reference-param-not-moved)
[[nodiscard]] constexpr Finalizer(Finalizer<F>&& that)
: Finalizer(std::move(that.function_), std::exchange(that.canceled_, true)) {}
// Move assignment
// requires(std::is_move_assignable_v<FinalFunction>)
constexpr auto operator=(Finalizer&& that) -> Finalizer& {
maybe_invoke();
function_ = std::move(that.function_);
canceled_ = std::exchange(that.canceled_, true);
return *this;
}
// Implicit conversion move assignment
// requires(!std::is_same_v<Finalizer, Finalizer<F>>)
template <typename F, typename = std::enable_if_t<!std::is_same_v<Finalizer, Finalizer<F>>>>
// NOLINTNEXTLINE(cppcoreguidelines-rvalue-reference-param-not-moved)
constexpr auto operator=(Finalizer<F>&& that) -> Finalizer& {
*this = Finalizer(std::move(that.function_), std::exchange(that.canceled_, true));
return *this;
}
// Cancels the final function, preventing it from being invoked.
constexpr void cancel() {
canceled_ = true;
maybe_nullify_function();
}
// Invokes the final function now, if not already invoked.
constexpr void operator()() { maybe_invoke(); }
private:
template <typename>
friend class Finalizer;
template <typename F, typename = std::enable_if_t<std::is_invocable_v<F>>>
[[nodiscard]] explicit constexpr Finalizer(F&& function, bool canceled)
: function_(std::forward<F>(function)), canceled_(canceled) {}
constexpr void maybe_invoke() {
if (!std::exchange(canceled_, true)) {
std::invoke(function_);
maybe_nullify_function();
}
}
constexpr void maybe_nullify_function() {
// Sets function_ to nullptr if that is supported for the backing type.
if constexpr (std::is_assignable_v<FinalFunction, nullptr_t>) {
function_ = nullptr;
}
}
FinalFunction function_;
bool canceled_ = true;
};
template <typename F>
Finalizer(F&&) -> Finalizer<std::decay_t<F>>;
// A standard alias for using `std::function` as the polymorphic function type.
using FinalizerStd = Finalizer<std::function<void()>>;
// Helpful aliases for using `ftl::Function` as the polymorphic function type.
using FinalizerFtl = Finalizer<Function<void()>>;
using FinalizerFtl1 = Finalizer<Function<void(), 1>>;
using FinalizerFtl2 = Finalizer<Function<void(), 2>>;
using FinalizerFtl3 = Finalizer<Function<void(), 3>>;
} // namespace android::ftl