blob: 4c339c0c130f49cdd7da0a0008b3a7387697889e [file] [log] [blame]
// Copyright 2023 The Pigweed Authors
//
// 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
//
// https://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 <type_traits>
// pw::Recyclable<T>
//
// Notes:
//
// pw::Recyclable<T> is a mix-in class which allows users to control what
// happens to objects when they reach the end of their lifecycle, as determined
// by the Pigweed managed pointer classes.
//
// The general idea is as follows. A developer might have some sort of factory
// pattern where they hand out unique_ptr<>s or IntrusivePtr<>s to objects which
// they have created. When their user is done with the object and the managed
// pointers let go of it, instead of executing the destructor and deleting the
// object, the developer may want to "recycle" the object and use it for some
// internal purpose. Examples include...
//
// 1) Putting the object on some sort of internal list to hand out again
// of the object is re-usable and the cost of construction/destruction
// is high.
// 2) Putting the object into some form of deferred destruction queue
// because users are either too high priority to pay the cost of
// destruction when the object is released, or because the act of
// destruction might involve operations which are not permitted when
// the object is released (perhaps the object is released at IRQ time,
// but the system needs to be running in a thread in order to properly
// clean up the object)
// 3) Re-using the object internally for something like bookkeeping
// purposes.
//
// In order to make use of the feature, users need to do two things.
//
// 1) Derive from pw::Recyclable<T>.
// 2) Implement a method with the signature "void pw_recycle()"
//
// When deriving from Recyclable<T>, T should be devoid of cv-qualifiers (even
// if the managed pointers handed out by the user's code are const or volatile).
// In addition, pw_recycle must be visible to pw::Recyclable<T>, either
// because it is public or because the T is friends with pw::Recyclable<T>.
//
// :: Example ::
//
// Some code hands out unique pointers to const Foo objects and wishes to
// have the chance to recycle them. The code would look something like
// this...
//
// class Foo : public pw::Recyclable<Foo> {
// public:
// // public implementation here
// private:
// friend class pw::Recyclable<Foo>;
// void pw_recycle() {
// if (should_recycle())) {
// do_recycle_stuff();
// } else {
// delete this;
// }
// }
// };
//
// Note: the intention is to use this feature with managed pointers,
// which will automatically detect and call the recycle method if
// present. That said, there is nothing to stop users for manually
// calling pw_recycle, provided that it is visible to the code which
// needs to call it.
namespace pw {
// Default implementation of pw::Recyclable.
//
// Note: we provide a default implementation instead of just a fwd declaration
// so we can add a static_assert which will give a user a more human readable
// error in case they make the mistake of deriving from pw::Recyclable<const
// Foo> instead of pw::Recyclable<Foo>
template <typename T, typename = void>
class Recyclable {
// Note: static assert must depend on T in order to trigger only when the
// template gets expanded. If it does not depend on any template parameters,
// eg static_assert(false), then it will always explode, regardless of whether
// or not the template is ever expanded.
static_assert(
std::is_same_v<T, T> == false,
"pw::Recyclable<T> objects must not specify cv-qualifiers for T. "
"Derive from pw::Recyclable<Foo>, not pw::Recyclable<const Foo>");
};
namespace internal {
// Test to see if an object is recyclable. An object of type T is considered to
// be recyclable if it derives from pw::Recyclable<T>
template <typename T>
inline constexpr bool has_pw_recycle_v =
std::is_base_of_v<::pw::Recyclable<std::remove_cv_t<T>>, T>;
template <typename T>
inline void recycle(T* ptr) {
static_assert(has_pw_recycle_v<T>, "T must derive from pw::Recyclable");
Recyclable<std::remove_cv_t<T>>::pw_recycle_thunk(
const_cast<std::remove_cv_t<T>*>(ptr));
}
} // namespace internal
template <typename T>
class Recyclable<T, std::enable_if_t<std::is_same_v<std::remove_cv_t<T>, T>>> {
private:
friend void ::pw::internal::recycle<T>(T*);
friend void ::pw::internal::recycle<const T>(const T*);
static void pw_recycle_thunk(T* ptr) {
static_assert(std::is_same_v<decltype(&T::pw_recycle), void (T::*)(void)>,
"pw_recycle() methods must be non-static member functions "
"with the signature 'void pw_recycle()', and be visible to "
"pw::Recyclable<T> (either because they are public, or "
"because of friendship).");
ptr->pw_recycle();
}
};
} // namespace pw