1 // Copyright 2023 The Pigweed Authors
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License"); you may not
4 // use this file except in compliance with the License. You may obtain a copy of
5 // the License at
6 //
7 // https://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12 // License for the specific language governing permissions and limitations under
13 // the License.
14 #pragma once
15
16 #include <type_traits>
17
18 // pw::Recyclable<T>
19 //
20 // Notes:
21 //
22 // pw::Recyclable<T> is a mix-in class which allows users to control what
23 // happens to objects when they reach the end of their lifecycle, as determined
24 // by the Pigweed managed pointer classes.
25 //
26 // The general idea is as follows. A developer might have some sort of factory
27 // pattern where they hand out unique_ptr<>s or IntrusivePtr<>s to objects which
28 // they have created. When their user is done with the object and the managed
29 // pointers let go of it, instead of executing the destructor and deleting the
30 // object, the developer may want to "recycle" the object and use it for some
31 // internal purpose. Examples include...
32 //
33 // 1) Putting the object on some sort of internal list to hand out again
34 // of the object is re-usable and the cost of construction/destruction
35 // is high.
36 // 2) Putting the object into some form of deferred destruction queue
37 // because users are either too high priority to pay the cost of
38 // destruction when the object is released, or because the act of
39 // destruction might involve operations which are not permitted when
40 // the object is released (perhaps the object is released at IRQ time,
41 // but the system needs to be running in a thread in order to properly
42 // clean up the object)
43 // 3) Re-using the object internally for something like bookkeeping
44 // purposes.
45 //
46 // In order to make use of the feature, users need to do two things.
47 //
48 // 1) Derive from pw::Recyclable<T>.
49 // 2) Implement a method with the signature "void pw_recycle()"
50 //
51 // When deriving from Recyclable<T>, T should be devoid of cv-qualifiers (even
52 // if the managed pointers handed out by the user's code are const or volatile).
53 // In addition, pw_recycle must be visible to pw::Recyclable<T>, either
54 // because it is public or because the T is friends with pw::Recyclable<T>.
55 //
56 // :: Example ::
57 //
58 // Some code hands out unique pointers to const Foo objects and wishes to
59 // have the chance to recycle them. The code would look something like
60 // this...
61 //
62 // class Foo : public pw::Recyclable<Foo> {
63 // public:
64 // // public implementation here
65 // private:
66 // friend class pw::Recyclable<Foo>;
67 // void pw_recycle() {
68 // if (should_recycle())) {
69 // do_recycle_stuff();
70 // } else {
71 // delete this;
72 // }
73 // }
74 // };
75 //
76 // Note: the intention is to use this feature with managed pointers,
77 // which will automatically detect and call the recycle method if
78 // present. That said, there is nothing to stop users for manually
79 // calling pw_recycle, provided that it is visible to the code which
80 // needs to call it.
81
82 namespace pw {
83
84 // Default implementation of pw::Recyclable.
85 //
86 // Note: we provide a default implementation instead of just a fwd declaration
87 // so we can add a static_assert which will give a user a more human readable
88 // error in case they make the mistake of deriving from pw::Recyclable<const
89 // Foo> instead of pw::Recyclable<Foo>
90 template <typename T, typename = void>
91 class Recyclable {
92 // Note: static assert must depend on T in order to trigger only when the
93 // template gets expanded. If it does not depend on any template parameters,
94 // eg static_assert(false), then it will always explode, regardless of whether
95 // or not the template is ever expanded.
96 static_assert(
97 std::is_same_v<T, T> == false,
98 "pw::Recyclable<T> objects must not specify cv-qualifiers for T. "
99 "Derive from pw::Recyclable<Foo>, not pw::Recyclable<const Foo>");
100 };
101
102 namespace internal {
103
104 // Test to see if an object is recyclable. An object of type T is considered to
105 // be recyclable if it derives from pw::Recyclable<T>
106 template <typename T>
107 inline constexpr bool has_pw_recycle_v =
108 std::is_base_of_v<::pw::Recyclable<std::remove_cv_t<T>>, T>;
109
110 template <typename T>
recycle(T * ptr)111 inline void recycle(T* ptr) {
112 static_assert(has_pw_recycle_v<T>, "T must derive from pw::Recyclable");
113 Recyclable<std::remove_cv_t<T>>::pw_recycle_thunk(
114 const_cast<std::remove_cv_t<T>*>(ptr));
115 }
116
117 } // namespace internal
118
119 template <typename T>
120 class Recyclable<T, std::enable_if_t<std::is_same_v<std::remove_cv_t<T>, T>>> {
121 private:
122 friend void ::pw::internal::recycle<T>(T*);
123 friend void ::pw::internal::recycle<const T>(const T*);
124
pw_recycle_thunk(T * ptr)125 static void pw_recycle_thunk(T* ptr) {
126 static_assert(std::is_same_v<decltype(&T::pw_recycle), void (T::*)(void)>,
127 "pw_recycle() methods must be non-static member functions "
128 "with the signature 'void pw_recycle()', and be visible to "
129 "pw::Recyclable<T> (either because they are public, or "
130 "because of friendship).");
131 ptr->pw_recycle();
132 }
133 };
134
135 } // namespace pw
136