| // Copyright 2023 The Chromium Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| // quiche_callbacks.h provides definitions for the callback types used by | |
| // QUICHE. Those aliases should be used instead of the function types provided | |
| // by the standard library (std::function) or Abseil (absl::FunctionRef, | |
| // absl::AnyInvocable). | |
| // | |
| // The aliases defined in this class are: | |
| // - quiche::UnretainedCallback | |
| // - quiche::SingleUseCallback | |
| // - quiche::MultiUseCallback | |
| // Each is documented below near its definition. | |
| // | |
| // As a general principle, there are following ways of constructing a callback: | |
| // - Using a lambda expression (preferred) | |
| // - Using absl::bind_front | |
| // - Passing an already defined local function | |
| // | |
| // The following methods, however, should be avoided: | |
| // - std::bind (<https://abseil.io/tips/108>) | |
| // - Binding or taking a pointer to a function outside of the current module, | |
| // especially the methods provided by the C++ standard library | |
| // (use lambda instead; see go/totw/133 internally for motivation) | |
| #ifndef QUICHE_COMMON_QUICHE_CALLBACKS_H_ | |
| #define QUICHE_COMMON_QUICHE_CALLBACKS_H_ | |
| #include <type_traits> | |
| #include "absl/functional/any_invocable.h" | |
| #include "absl/functional/function_ref.h" | |
| #include "quiche/common/platform/api/quiche_export.h" | |
| namespace quiche { | |
| namespace callbacks_internal { | |
| template <class Sig> | |
| class QUICHE_EXPORT SignatureChanger {}; | |
| template <typename ReturnType, typename... Args> | |
| class QUICHE_NO_EXPORT SignatureChanger<ReturnType(Args...)> { | |
| public: | |
| using Rvalue = ReturnType(Args...) &&; | |
| using Const = ReturnType(Args...) const; | |
| }; | |
| } // namespace callbacks_internal | |
| // UnretainedCallback is the QUICHE alias for absl::FunctionRef. | |
| // | |
| // UnretainedCallback should be used when a function needs another function | |
| // passed into it, but it will not retain any pointers to it long-term. | |
| // | |
| // For example, a QuicSession class may have function: | |
| // void DoForAllStreams(quiche::UnretainedCallback<void(QuicStream*)>); | |
| // | |
| // Then a method could call it like this: | |
| // int num_bidi_streams = 0; | |
| // DoForAllStreams([&num_bidi_streams](QuicStream* stream) { | |
| // if (stream->is_bidirectional()) { | |
| // ++num_bidi_streams; | |
| // } | |
| // }); | |
| // | |
| // Note that similarly to absl::string_view, FunctionRef/UnretainedCallback does | |
| // not own the underlying memory. This means that the code below will not work: | |
| // | |
| // quiche::UnretainedCallback f = [&i]() { ++i; } // <- INVALID CODE | |
| // | |
| // The code above allocates a lambda object that stores a pointer to `i` in it, | |
| // stores a reference to that object inside `f`, and then immediately frees the | |
| // lambda object. Attempting to compile the code above will fail with an error | |
| // that says "Temporary whose address is used as value of local variable 'f' | |
| // will be destroyed at the end of the full-expression". | |
| template <class T> | |
| using UnretainedCallback = absl::FunctionRef<T>; | |
| // SingleUseCallback<T(...)> is the QUICHE alias for | |
| // absl::AnyInvocable<T(...) &&>. | |
| // | |
| // SingleUseCallback is meant to be used for callbacks that may be called at | |
| // most once. For instance, a class may have a method that looks like this: | |
| // | |
| // void SetOnSessionDestroyed(quiche::SingleUseCallback<void()> callback) { | |
| // on_session_destroyed_callback_ = std::move(callback); | |
| // } | |
| // | |
| // Then it can execute the callback like this: | |
| // | |
| // ~Session() { | |
| // std::move(on_session_destroyed_callback_ )(); | |
| // } | |
| // | |
| // Note that as with other types of similar nature, calling the callback after | |
| // it has been moved is undefined behavior (it will result in an | |
| // ABSL_HARDENING_ASSERT() call). | |
| template <class T> | |
| using SingleUseCallback = absl::AnyInvocable< | |
| typename callbacks_internal::SignatureChanger<T>::Rvalue>; | |
| static_assert(std::is_same_v<SingleUseCallback<void(int, int &, int &&)>, | |
| absl::AnyInvocable<void(int, int &, int &&) &&>>); | |
| // MultiUseCallback<T(...)> is the QUICHE alias for | |
| // absl::AnyInvocable<T(...) const>. | |
| // | |
| // MultiUseCallback is intended for situations where a callback may be invoked | |
| // multiple times. It is probably the closest equivalent to std::function | |
| // in this file, notable differences being that MultiUseCallback is move-only | |
| // and immutable (meaning that it cannot have an internal state that mutates; it | |
| // can still point to things that are mutable). | |
| template <class T> | |
| using MultiUseCallback = | |
| absl::AnyInvocable<typename callbacks_internal::SignatureChanger<T>::Const>; | |
| static_assert( | |
| std::is_same_v<MultiUseCallback<void()>, absl::AnyInvocable<void() const>>); | |
| // Use cases that are intentionally not covered by this header file: | |
| // | |
| // (a) Mutable callbacks. | |
| // | |
| // In C++, it's possible for a lambda to mutate its own state, e.g.: | |
| // | |
| // absl::AnyInvocable<void()> inc = [i = 0]() mutable { | |
| // std::cout << (i++) << std::endl; | |
| // }; | |
| // inc(); | |
| // inc(); | |
| // inc(); | |
| // | |
| // The code above will output numbers 0, 1, 2. The fact that a callback can | |
| // mutate its internal representation is counterintuitive, and thus not | |
| // supported. Note that this limitation can be trivially worked around by | |
| // passing a pointer (e.g., in the example below, `i = 0` could be replaced with | |
| // `i = std::make_unique<int>(0)` to the similar effect). | |
| // | |
| // (b) Copyable callbacks. | |
| // | |
| // In C++, this would typically achieved by using std::function. This file | |
| // could contain an alias for std::function; it currently does not, since this | |
| // use case is expected to be fairly rare. | |
| // | |
| // (c) noexpect support. | |
| // | |
| // We do not use C++ exceptions in QUICHE. | |
| } // namespace quiche | |
| #endif // QUICHE_COMMON_QUICHE_CALLBACKS_H_ |