1 /** 2 * Dynamic Array 3 * Copyright: © 2015 Economic Modeling Specialists, Intl. 4 * Authors: Brian Schott 5 * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) 6 */ 7 8 module containers.dynamicarray; 9 10 private import containers.internal.node : shouldAddGCRange; 11 private import std.experimental.allocator.mallocator : Mallocator; 12 13 /** 14 * Array that is able to grow itself when items are appended to it. Uses 15 * malloc/free/realloc to manage its storage. 16 * 17 * Params: 18 * T = the array element type 19 * Allocator = the allocator to use. Defaults to `Mallocator`. 20 * supportGC = true if the container should support holding references to 21 * GC-allocated memory. 22 */ 23 struct DynamicArray(T, Allocator = Mallocator, bool supportGC = shouldAddGCRange!T) 24 { 25 this(this) @disable; 26 27 private import std.experimental.allocator.common : stateSize; 28 29 static if (stateSize!Allocator != 0) 30 { 31 /// No default construction if an allocator must be provided. 32 this() @disable; 33 34 /** 35 * Use the given `allocator` for allocations. 36 */ 37 this(Allocator allocator) 38 in 39 { 40 assert(allocator !is null, "Allocator must not be null"); 41 } 42 body 43 { 44 this.allocator = allocator; 45 } 46 } 47 48 ~this() 49 { 50 import std.experimental.allocator.mallocator : Mallocator; 51 import containers.internal.node : shouldAddGCRange; 52 53 if (arr is null) 54 return; 55 foreach (ref item; arr[0 .. l]) 56 { 57 static if (is(T == class)) 58 destroy(item); 59 else 60 typeid(T).destroy(&item); 61 } 62 static if (shouldAddGCRange!T) 63 { 64 import core.memory : GC; 65 GC.removeRange(arr.ptr); 66 } 67 allocator.deallocate(arr); 68 } 69 70 /// Slice operator overload 71 pragma(inline, true) 72 auto opSlice(this This)() @nogc 73 { 74 return opSlice!(This)(0, l); 75 } 76 77 /// ditto 78 pragma(inline, true) 79 auto opSlice(this This)(size_t a, size_t b) @nogc 80 { 81 alias ET = ContainerElementType!(This, T); 82 return cast(ET[]) arr[a .. b]; 83 } 84 85 /// Index operator overload 86 pragma(inline, true) 87 auto opIndex(this This)(size_t i) @nogc 88 { 89 return opSlice!(This)(i, i + 1)[0]; 90 } 91 92 /** 93 * Inserts the given value into the end of the array. 94 */ 95 void insert(T value) 96 { 97 import std.experimental.allocator.mallocator : Mallocator; 98 import containers.internal.node : shouldAddGCRange; 99 100 if (arr.length == 0) 101 { 102 arr = cast(typeof(arr)) allocator.allocate(T.sizeof * 4); 103 static if (useGC) 104 { 105 import core.memory: GC; 106 GC.addRange(arr.ptr, arr.length * T.sizeof); 107 } 108 } 109 else if (l >= arr.length) 110 { 111 immutable size_t c = arr.length > 512 ? arr.length + 1024 : arr.length << 1; 112 void[] a = cast(void[]) arr; 113 allocator.reallocate(a, c * T.sizeof); 114 arr = cast(typeof(arr)) a; 115 static if (useGC) 116 { 117 import core.memory: GC; 118 GC.removeRange(arr.ptr); 119 GC.addRange(arr.ptr, arr.length * T.sizeof); 120 } 121 } 122 arr[l++] = value; 123 } 124 125 /// ditto 126 alias put = insert; 127 128 void remove(const size_t i) 129 { 130 if (i < this.l) 131 { 132 static if (is(T == class)) 133 destroy(arr[i]); 134 else 135 typeid(T).destroy(&arr[i]); 136 137 auto next = i + 1; 138 while (next < this.l) 139 { 140 arr[next - 1] = arr[next]; 141 ++next; 142 } 143 144 --l; 145 } 146 else 147 { 148 import core.exception : RangeError; 149 throw new RangeError("Out of range index used to remove element"); 150 } 151 } 152 153 /// Index assignment support 154 void opIndexAssign(T value, size_t i) @nogc 155 { 156 arr[i] = value; 157 } 158 159 /// Slice assignment support 160 void opSliceAssign(T value) @nogc 161 { 162 arr[0 .. l] = value; 163 } 164 165 /// ditto 166 void opSliceAssign(T value, size_t i, size_t j) @nogc 167 { 168 arr[i .. j] = value; 169 } 170 171 /// Returns: the number of items in the array 172 size_t length() const nothrow pure @property @safe @nogc { return l; } 173 174 /// Returns: whether or not the DynamicArray is empty. 175 bool empty() const nothrow pure @property @safe @nogc { return l == 0; } 176 177 /** 178 * Returns: a slice to the underlying array. 179 * 180 * As the memory of the array may be freed, access to this array is 181 * highly unsafe. 182 */ 183 auto ptr(this This)() @nogc @property 184 { 185 alias ET = ContainerElementType!(This, T); 186 return cast(ET*) arr.ptr; 187 } 188 189 /// Returns: the front element of the DynamicArray. 190 auto ref T front() pure @property 191 { 192 return arr[0]; 193 } 194 195 /// Returns: the back element of the DynamicArray. 196 auto ref T back() pure @property 197 { 198 return arr[l - 1]; 199 } 200 201 private: 202 203 import containers.internal.storage_type : ContainerStorageType; 204 import containers.internal.element_type : ContainerElementType; 205 import containers.internal.mixins : AllocatorState; 206 207 enum bool useGC = supportGC && shouldAddGCRange!T; 208 mixin AllocatorState!Allocator; 209 ContainerStorageType!(T)[] arr; 210 size_t l; 211 } 212 213 unittest 214 { 215 import std.algorithm : equal; 216 import std.range : iota; 217 DynamicArray!int ints; 218 assert(ints.empty); 219 foreach (i; 0 .. 100) 220 { 221 ints.insert(i); 222 assert(ints.front == 0); 223 assert(ints.back == i); 224 } 225 226 assert (equal(ints[], iota(100))); 227 assert (ints.length == 100); 228 ints[0] = 100; 229 assert (ints[0] == 100); 230 ints[0 .. 5] = 20; 231 foreach (i; ints[0 .. 5]) 232 assert (i == 20); 233 ints[] = 432; 234 foreach (i; ints[]) 235 assert (i == 432); 236 237 auto arr = ints.ptr; 238 arr[0] = 1337; 239 assert(arr[0] == 1337); 240 assert(ints[0] == 1337); 241 } 242 243 version(unittest) 244 { 245 class Cls 246 { 247 int* a; 248 249 this(int* a) 250 { 251 this.a = a; 252 } 253 254 ~this() 255 { 256 ++(*a); 257 } 258 } 259 } 260 261 unittest 262 { 263 int a = 0; 264 { 265 DynamicArray!(Cls) arr; 266 arr.insert(new Cls( & a)); 267 } 268 assert(a == 1); 269 } 270 271 unittest 272 { 273 import std.exception : assertThrown; 274 import core.exception : RangeError; 275 DynamicArray!int empty; 276 assertThrown!RangeError(empty.remove(1337)); 277 assert(empty.length == 0); 278 279 DynamicArray!int one; 280 one.insert(0); 281 assert(one.length == 1); 282 assertThrown!RangeError(one.remove(1337)); 283 assert(one.length == 1); 284 one.remove(0); 285 assert(one.length == 0); 286 287 DynamicArray!int two; 288 two.insert(0); 289 two.insert(1); 290 assert(two.length == 2); 291 assertThrown!RangeError(two.remove(1337)); 292 assert(two.length == 2); 293 two.remove(0); 294 assert(two.length == 1); 295 assert(two[0] == 1); 296 two.remove(0); 297 assert(two.length == 0); 298 299 two.insert(0); 300 two.insert(1); 301 assert(two.length == 2); 302 303 two.remove(1); 304 assert(two.length == 1); 305 assert(two[0] == 0); 306 assertThrown!RangeError(two.remove(1)); 307 assert(two.length == 1); 308 assert(two[0] == 0); 309 two.remove(0); 310 assert(two.length == 0); 311 } 312 313 unittest 314 { 315 int a = 0; 316 DynamicArray!(Cls, Mallocator, true) arr; 317 arr.insert(new Cls(&a)); 318 319 arr.remove(0); 320 assert(a == 1); 321 } 322 323 unittest 324 { 325 DynamicArray!(int*, Mallocator, true) arr; 326 arr.insert(new int(1)); 327 }