1 /**
2 * Basic reference-counting for classes.
3 *
4 * License:
5 * This Source Code Form is subject to the terms of
6 * the Mozilla Public License, v. 2.0. If a copy of
7 * the MPL was not distributed with this file, You
8 * can obtain one at http://mozilla.org/MPL/2.0/.
9 *
10 * Authors:
11 * Vladimir Panteleev <ae@cy.md>
12 */
13
14 module ae.utils.meta.rcclass;
15
16 import core.memory;
17
18 import std.conv : emplace;
19
20 private struct RCClassStore(C)
21 {
22 size_t refCount = void;
23 void[__traits(classInstanceSize, C)] data = void;
24 }
25
26 /// Wraps class type `C` into a reference-counting value type.
27 struct RCClass(C)
28 if (is(C == class))
29 {
30 // storage
31
32 private RCClassStore!C* _rcClassStore;
33
34 @property C _rcClassGet()
35 {
36 return cast(C)_rcClassStore.data.ptr;
37 }
38
39 alias _rcClassGet this;
40
41 // construction
42
43 this(T)(T value)
44 if (is(T == RCClass!U, U) && is(typeof({U u; C c = u;})))
45 {
46 _rcClassStore = cast(RCClassStore!C*)value._rcClassStore;
47 if (_rcClassStore)
48 _rcClassStore.refCount++;
49 }
50
51 // operations
52
53 ref typeof(this) opAssign(T)(T value)
54 if (is(T == typeof(null)))
55 {
56 _rcClassDestroy();
57 _rcClassStore = null;
58 return this;
59 } ///
60
61 ref typeof(this) opAssign(T)(auto ref T value)
62 if (is(T == RCClass!U, U) && is(typeof({U u; C c = u;})))
63 {
64 _rcClassDestroy();
65 _rcClassStore = cast(RCClassStore!C*)value._rcClassStore;
66 if (_rcClassStore)
67 _rcClassStore.refCount++;
68 return this;
69 } ///
70
71 T opCast(T)()
72 if (is(T == RCClass!U, U) && is(typeof({C c; U u = c;})))
73 {
74 T result;
75 result._rcClassStore = cast(typeof(result._rcClassStore))_rcClassStore;
76 if (_rcClassStore)
77 _rcClassStore.refCount++;
78 return result;
79 } ///
80
81 bool opCast(T)()
82 if (is(T == bool))
83 {
84 return !!_rcClassStore;
85 } ///
86
87 auto opCall(Args...)(auto ref Args args)
88 if (is(typeof(_rcClassGet.opCall(args))))
89 {
90 return _rcClassGet.opCall(args);
91 } ///
92
93 // lifetime
94
95 void _rcClassDestroy()
96 {
97 if (_rcClassStore && --_rcClassStore.refCount == 0)
98 {
99 static if (__traits(hasMember, C, "__xdtor"))
100 _rcClassGet.__xdtor();
101 GC.free(_rcClassStore);
102 }
103 }
104
105 this(this)
106 {
107 if (_rcClassStore)
108 _rcClassStore.refCount++;
109 }
110
111 ~this()
112 {
113 _rcClassDestroy();
114 }
115 }
116
117 /// Constructs a new reference-counted instance of `C`.
118 /// (An external factory function is used instead of `static opCall`
119 /// to avoid conflicting with the class's non-static `opCall`.)
120 template rcClass(C)
121 if (is(C == class))
122 {
123 RCClass!C rcClass(Args...)(auto ref Args args)
124 if (is(C == class) && is(typeof(emplace!C(null, args))))
125 {
126 RCClass!C c;
127 c._rcClassStore = new RCClassStore!C;
128 c._rcClassStore.refCount = 1;
129 emplace!C(c._rcClassStore.data[], args);
130 return c;
131 }
132 }
133
134 /// Constructors
135 unittest
136 {
137 void ctorTest(bool haveArglessCtor, bool haveArgCtor)()
138 {
139 static class C
140 {
141 int n = -1;
142
143 static if (haveArglessCtor)
144 this() { n = 1; }
145
146 static if (haveArgCtor)
147 this(int val) { n = val; }
148
149 ~this() { n = -2; }
150 }
151
152 RCClass!C rc;
153 assert(!rc);
154
155 static if (haveArglessCtor || !haveArgCtor)
156 {
157 rc = rcClass!C();
158 assert(rc);
159 static if (haveArglessCtor)
160 assert(rc.n == 1);
161 else
162 assert(rc.n == -1); // default value
163 }
164 else
165 static assert(!is(typeof(rcClass!C())));
166
167 static if (haveArgCtor)
168 {
169 rc = rcClass!C(42);
170 assert(rc);
171 assert(rc.n == 42);
172 }
173 else
174 static assert(!is(typeof(rcClass!C(1))));
175
176 rc = null;
177 assert(!rc);
178 }
179
180 import std.meta : AliasSeq;
181 foreach (haveArglessCtor; AliasSeq!(false, true))
182 foreach (haveArgCtor; AliasSeq!(false, true))
183 ctorTest!(haveArglessCtor, haveArgCtor);
184 }
185
186 /// Lifetime
187 unittest
188 {
189 static class C
190 {
191 static int counter;
192
193 this() { counter++; }
194 ~this() { counter--; }
195 }
196
197 {
198 auto a = rcClass!C();
199 assert(C.counter == 1);
200 auto b = a;
201 assert(C.counter == 1);
202 }
203 assert(C.counter == 0);
204 }
205
206 /// Inheritance
207 unittest
208 {
209 static class Base
210 {
211 int foo() { return 1; }
212 }
213
214 static class Derived : Base
215 {
216 override int foo() { return 2; }
217 }
218
219 auto derived = rcClass!Derived();
220 RCClass!Base base = derived; // initialization
221 base = derived; // assignment
222 static assert(!is(typeof(derived = base)));
223 auto base2 = cast(RCClass!Base)derived;
224 }
225
226 /// Non-static opCall
227 unittest
228 {
229 static class C
230 {
231 int calls;
232 void opCall() { calls++; }
233 }
234
235 auto c = rcClass!C();
236 assert(c.calls == 0);
237 c();
238 assert(c.calls == 1);
239 }