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 core.lifetime : move, moveEmplace, copyEmplace, emplace; 11 private import std.traits : isCopyable; 12 private import containers.internal.node : shouldAddGCRange; 13 private import std.experimental.allocator.mallocator : Mallocator; 14 15 /** 16 * Array that is able to grow itself when items are appended to it. Uses 17 * malloc/free/realloc to manage its storage. 18 * 19 * Params: 20 * T = the array element type 21 * Allocator = the allocator to use. Defaults to `Mallocator`. 22 * supportGC = true if the container should support holding references to 23 * GC-allocated memory. 24 */ 25 struct DynamicArray(T, Allocator = Mallocator, bool supportGC = shouldAddGCRange!T) 26 { 27 this(this) @disable; 28 29 private import std.experimental.allocator.common : stateSize; 30 31 static if (is(typeof((T[] a, const T[] b) => a[0 .. b.length] = b[0 .. $]))) 32 { 33 /// Either `const(T)` or `T`. 34 alias AppendT = const(T); 35 36 /// Either `const(typeof(this))` or `typeof(this)`. 37 alias AppendTypeOfThis = const(typeof(this)); 38 } 39 else 40 { 41 alias AppendT = T; 42 alias AppendTypeOfThis = typeof(this); 43 } 44 45 static if (stateSize!Allocator != 0) 46 { 47 /// No default construction if an allocator must be provided. 48 this() @disable; 49 50 /** 51 * Use the given `allocator` for allocations. 52 */ 53 this(Allocator allocator) 54 in 55 { 56 static if (is(typeof(allocator is null))) 57 assert(allocator !is null, "Allocator must not be null"); 58 } 59 do 60 { 61 this.allocator = allocator; 62 } 63 } 64 65 ~this() 66 { 67 import std.experimental.allocator.mallocator : Mallocator; 68 import containers.internal.node : shouldAddGCRange; 69 70 if (arr is null) 71 return; 72 73 static if ((is(T == struct) || is(T == union)) 74 && __traits(hasMember, T, "__xdtor")) 75 { 76 foreach (ref item; arr[0 .. l]) 77 { 78 item.__xdtor(); 79 } 80 } 81 static if (useGC) 82 { 83 import core.memory : GC; 84 GC.removeRange(arr.ptr); 85 } 86 allocator.deallocate(arr); 87 } 88 89 /// Slice operator overload 90 pragma(inline, true) 91 auto opSlice(this This)() @nogc 92 { 93 return opSlice!(This)(0, l); 94 } 95 96 /// ditto 97 pragma(inline, true) 98 auto opSlice(this This)(size_t a, size_t b) @nogc 99 { 100 alias ET = ContainerElementType!(This, T); 101 return cast(ET[]) arr[a .. b]; 102 } 103 104 /// Index operator overload 105 pragma(inline, true) 106 ref auto opIndex(this This)(size_t i) @nogc 107 { 108 return opSlice!(This)(i, i + 1)[0]; 109 } 110 111 /** 112 * Inserts the given value into the end of the array. 113 */ 114 void insertBack(T value) 115 { 116 import std.experimental.allocator.mallocator : Mallocator; 117 import containers.internal.node : shouldAddGCRange; 118 119 if (arr.length == 0) 120 { 121 arr = cast(typeof(arr)) allocator.allocate(T.sizeof * 4); 122 static if (useGC) 123 { 124 import core.memory: GC; 125 GC.addRange(arr.ptr, arr.length * T.sizeof); 126 } 127 } 128 else if (l >= arr.length) 129 { 130 immutable size_t c = arr.length > 512 ? arr.length + 1024 : arr.length << 1; 131 static if (useGC) 132 void* oldPtr = arr.ptr; 133 void[] a = cast(void[]) arr; 134 import std.experimental.allocator.common : reallocate; 135 allocator.reallocate(a, c * T.sizeof); 136 arr = cast(typeof(arr)) a; 137 static if (useGC) 138 { 139 import core.memory: GC; 140 GC.removeRange(oldPtr); 141 GC.addRange(arr.ptr, arr.length * T.sizeof); 142 } 143 } 144 moveEmplace(*cast(ContainerStorageType!T*)&value, arr[l++]); 145 } 146 147 /// ditto 148 alias insert = insertBack; 149 150 /// ditto 151 alias insertAnywhere = insertBack; 152 153 /// ditto 154 alias put = insertBack; 155 156 /** 157 * ~= operator overload 158 */ 159 scope ref typeof(this) opOpAssign(string op)(T value) if (op == "~") 160 { 161 insert(value); 162 return this; 163 } 164 165 /** 166 * ~= operator overload for an array of items 167 */ 168 scope ref typeof(this) opOpAssign(string op, bool checkForOverlap = true)(AppendT[] rhs) 169 if (op == "~" && !is(T == AppendT[])) 170 { 171 // Disabling checkForOverlap when this function is called from opBinary!"~" 172 // is not just for efficiency, but to avoid circular function calls that 173 // would prevent inference of @nogc, etc. 174 static if (checkForOverlap) 175 if ((() @trusted => arr.ptr <= rhs.ptr && arr.ptr + arr.length > rhs.ptr)()) 176 { 177 // Special case where rhs is a slice of this array. 178 this = this ~ rhs; 179 return this; 180 } 181 reserve(l + rhs.length); 182 import std.traits: hasElaborateAssign, hasElaborateDestructor; 183 static if (is(T == struct) && (hasElaborateAssign!T || hasElaborateDestructor!T)) 184 { 185 foreach (ref value; rhs) 186 copyEmplace(value, arr[l++]); 187 } 188 else 189 { 190 arr[l .. l + rhs.length] = rhs[0 .. rhs.length]; 191 l += rhs.length; 192 } 193 return this; 194 } 195 196 /// ditto 197 scope ref typeof(this) opOpAssign(string op)(ref AppendTypeOfThis rhs) 198 if (op == "~") 199 { 200 return this ~= rhs.arr[0 .. rhs.l]; 201 } 202 203 /** 204 * ~ operator overload 205 */ 206 typeof(this) opBinary(string op)(ref AppendTypeOfThis other) if (op == "~") 207 { 208 typeof(this) ret; 209 ret.reserve(l + other.l); 210 ret.opOpAssign!("~", false)(arr[0 .. l]); 211 ret.opOpAssign!("~", false)(other.arr[0 .. other.l]); 212 return ret; 213 } 214 215 /// ditto 216 typeof(this) opBinary(string op)(AppendT[] values) if (op == "~") 217 { 218 typeof(this) ret; 219 ret.reserve(l + values.length); 220 ret.opOpAssign!("~", false)(arr[0 .. l]); 221 ret.opOpAssign!("~", false)(values); 222 return ret; 223 } 224 225 /** 226 * Ensures sufficient capacity to accommodate `n` elements. 227 */ 228 void reserve(size_t n) 229 { 230 if (arr.length >= n) 231 return; 232 if (arr.ptr is null) 233 { 234 size_t c = 4; 235 if (c < n) 236 c = n; 237 arr = cast(typeof(arr)) allocator.allocate(T.sizeof * c); 238 static if (useGC) 239 { 240 import core.memory: GC; 241 GC.addRange(arr.ptr, arr.length * T.sizeof); 242 } 243 } 244 else 245 { 246 size_t c = arr.length > 512 ? arr.length + 1024 : arr.length << 1; 247 if (c < n) 248 c = n; 249 static if (useGC) 250 void* oldPtr = arr.ptr; 251 void[] a = cast(void[]) arr; 252 import std.experimental.allocator.common : reallocate; 253 allocator.reallocate(a, c * T.sizeof); 254 arr = cast(typeof(arr)) a; 255 static if (useGC) 256 { 257 import core.memory: GC; 258 GC.removeRange(oldPtr); 259 GC.addRange(arr.ptr, arr.length * T.sizeof); 260 } 261 } 262 } 263 264 /** 265 * Change the array length. 266 * When growing, initialize new elements to the default value. 267 */ 268 static if (is(typeof({static T value;}))) // default construction is allowed 269 void resize(size_t n) 270 { 271 import std.traits: hasElaborateAssign, hasElaborateDestructor; 272 auto toFill = resizeStorage(n); 273 static if (is(T == struct) && hasElaborateDestructor!T) 274 { 275 foreach (ref target; toFill) 276 emplace(&target); 277 } 278 else 279 toFill[] = T.init; 280 } 281 282 /** 283 * Change the array length. 284 * When growing, initialize new elements to the given value. 285 */ 286 static if (isCopyable!T) 287 void resize(size_t n, T value) 288 { 289 import std.traits: hasElaborateAssign, hasElaborateDestructor; 290 auto toFill = resizeStorage(n); 291 static if (is(T == struct) && (hasElaborateAssign!T || hasElaborateDestructor!T)) 292 { 293 foreach (ref target; toFill) 294 copyEmplace(value, target); 295 } 296 else 297 toFill[] = value; 298 } 299 300 // Resizes storage only, and returns slice of new memory to fill. 301 private ContainerStorageType!T[] resizeStorage(size_t n) 302 { 303 ContainerStorageType!T[] toFill = null; 304 305 if (arr.length < n) 306 reserve(n); 307 308 if (l < n) // Growing? 309 { 310 toFill = arr[l..n]; 311 } 312 else 313 { 314 static if ((is(T == struct) || is(T == union)) 315 && __traits(hasMember, T, "__xdtor")) 316 { 317 foreach (i; n..l) 318 arr[i].__xdtor(); 319 } 320 } 321 322 l = n; 323 return toFill; 324 } 325 326 /** 327 * Remove the item at the given index from the array. 328 */ 329 void remove(const size_t i) 330 { 331 if (i < this.l) 332 { 333 auto next = i + 1; 334 while (next < this.l) 335 { 336 move(arr[next], arr[next - 1]); 337 ++next; 338 } 339 340 --l; 341 static if ((is(T == struct) || is(T == union)) 342 && __traits(hasMember, T, "__xdtor")) 343 { 344 arr[l].__xdtor(); 345 } 346 } 347 else 348 { 349 import core.exception : RangeError; 350 throw new RangeError("Out of range index used to remove element"); 351 } 352 } 353 354 /** 355 * Removes the last element from the array. 356 */ 357 void removeBack() 358 { 359 this.remove(this.length - 1); 360 } 361 362 /// Index assignment support 363 void opIndexAssign(T value, size_t i) @nogc 364 { 365 arr[i] = move(*cast(ContainerStorageType!T*)&value); 366 } 367 368 /// Slice assignment support 369 static if (isCopyable!T) 370 void opSliceAssign(T value) @nogc 371 { 372 arr[0 .. l] = value; 373 } 374 375 /// ditto 376 static if (isCopyable!T) 377 void opSliceAssign(T value, size_t i, size_t j) @nogc 378 { 379 arr[i .. j] = value; 380 } 381 382 /// ditto 383 static if (isCopyable!T) 384 void opSliceAssign(T[] values) @nogc 385 { 386 arr[0 .. l] = values[]; 387 } 388 389 /// ditto 390 static if (isCopyable!T) 391 void opSliceAssign(T[] values, size_t i, size_t j) @nogc 392 { 393 arr[i .. j] = values[]; 394 } 395 396 /// Returns: the number of items in the array 397 size_t length() const nothrow pure @property @safe @nogc { return l; } 398 399 /// Ditto 400 alias opDollar = length; 401 402 /// Returns: whether or not the DynamicArray is empty. 403 bool empty() const nothrow pure @property @safe @nogc { return l == 0; } 404 405 /** 406 * Returns: a slice to the underlying array. 407 * 408 * As the memory of the array may be freed, access to this array is 409 * highly unsafe. 410 */ 411 auto ptr(this This)() @nogc @property 412 { 413 alias ET = ContainerElementType!(This, T); 414 return cast(ET*) arr.ptr; 415 } 416 417 /// Returns: the front element of the DynamicArray. 418 auto ref T front() pure @property 419 { 420 return arr[0]; 421 } 422 423 /// Returns: the back element of the DynamicArray. 424 auto ref T back() pure @property 425 { 426 return arr[l - 1]; 427 } 428 429 private: 430 431 import containers.internal.storage_type : ContainerStorageType; 432 import containers.internal.element_type : ContainerElementType; 433 import containers.internal.mixins : AllocatorState; 434 435 enum bool useGC = supportGC && shouldAddGCRange!T; 436 mixin AllocatorState!Allocator; 437 ContainerStorageType!(T)[] arr; 438 size_t l; 439 } 440 441 version(emsi_containers_unittest) unittest 442 { 443 import std.algorithm : equal; 444 import std.range : iota; 445 DynamicArray!int ints; 446 assert(ints.empty); 447 foreach (i; 0 .. 100) 448 { 449 ints.insert(i); 450 assert(ints.front == 0); 451 assert(ints.back == i); 452 } 453 454 assert (equal(ints[], iota(100))); 455 assert (ints.length == 100); 456 ints[0] = 100; 457 assert (ints[0] == 100); 458 ints[0 .. 5] = 20; 459 foreach (i; ints[0 .. 5]) 460 assert (i == 20); 461 ints[] = 432; 462 foreach (i; ints[]) 463 assert (i == 432); 464 465 auto arr = ints.ptr; 466 arr[0] = 1337; 467 assert(arr[0] == 1337); 468 assert(ints[0] == 1337); 469 } 470 471 version(emsi_containers_unittest) 472 { 473 class Cls 474 { 475 int* a; 476 477 this(int* a) 478 { 479 this.a = a; 480 } 481 482 ~this() 483 { 484 ++(*a); 485 } 486 } 487 } 488 489 version(emsi_containers_unittest) unittest 490 { 491 int* a = new int; 492 { 493 DynamicArray!(Cls) arr; 494 arr.insert(new Cls(a)); 495 } 496 assert(*a == 0); // Destructor not called. 497 } 498 499 version(emsi_containers_unittest) unittest 500 { 501 import std.exception : assertThrown; 502 import core.exception : RangeError; 503 DynamicArray!int empty; 504 assertThrown!RangeError(empty.remove(1337)); 505 assert(empty.length == 0); 506 507 DynamicArray!int one; 508 one.insert(0); 509 assert(one.length == 1); 510 assertThrown!RangeError(one.remove(1337)); 511 assert(one.length == 1); 512 one.remove(0); 513 assert(one.length == 0); 514 515 DynamicArray!int two; 516 two.insert(0); 517 two.insert(1); 518 assert(two.length == 2); 519 assertThrown!RangeError(two.remove(1337)); 520 assert(two.length == 2); 521 two.remove(0); 522 assert(two.length == 1); 523 assert(two[0] == 1); 524 two.remove(0); 525 assert(two.length == 0); 526 527 two.insert(0); 528 two.insert(1); 529 assert(two.length == 2); 530 531 two.remove(1); 532 assert(two.length == 1); 533 assert(two[0] == 0); 534 assertThrown!RangeError(two.remove(1)); 535 assert(two.length == 1); 536 assert(two[0] == 0); 537 two.remove(0); 538 assert(two.length == 0); 539 } 540 541 version(emsi_containers_unittest) unittest 542 { 543 int* a = new int; 544 DynamicArray!(Cls, Mallocator, true) arr; 545 arr.insert(new Cls(a)); 546 547 arr.remove(0); 548 assert(*a == 0); // Destructor not called. 549 } 550 551 version(emsi_containers_unittest) unittest 552 { 553 DynamicArray!(int*, Mallocator, true) arr; 554 555 foreach (i; 0 .. 4) 556 arr.insert(new int(i)); 557 558 assert (arr.length == 4); 559 560 int*[] slice = arr[1 .. $ - 1]; 561 assert (slice.length == 2); 562 assert (*slice[0] == 1); 563 assert (*slice[1] == 2); 564 } 565 566 version(emsi_containers_unittest) unittest 567 { 568 import std.format : format; 569 570 DynamicArray!int arr; 571 foreach (int i; 0 .. 10) 572 arr ~= i; 573 assert(arr.length == 10, "arr.length = %d".format(arr.length)); 574 575 auto arr2 = arr ~ arr; 576 assert(arr2.length == 20); 577 auto arr3 = arr2 ~ [100, 99, 98]; 578 assert(arr3.length == 23); 579 580 while(!arr3.empty) 581 arr3.removeBack(); 582 assert(arr3.empty); 583 584 } 585 586 version(emsi_containers_unittest) @system unittest 587 { 588 DynamicArray!int a; 589 a.reserve(1000); 590 assert(a.length == 0); 591 assert(a.empty); 592 assert(a.arr.length >= 1000); 593 int* p = a[].ptr; 594 foreach (i; 0 .. 1000) 595 { 596 a.insert(i); 597 } 598 assert(p is a[].ptr); 599 } 600 601 version(emsi_containers_unittest) unittest 602 { 603 // Ensure that Array.insert doesn't call the destructor for 604 // a struct whose state is uninitialized memory. 605 static struct S 606 { 607 int* a; 608 ~this() @nogc nothrow 609 { 610 if (a !is null) 611 ++(*a); 612 } 613 } 614 int* a = new int; 615 { 616 DynamicArray!S arr; 617 // This next line may segfault if destructors are called 618 // on structs in invalid states. 619 arr.insert(S(a)); 620 } 621 assert(*a == 1); 622 } 623 624 version(emsi_containers_unittest) @nogc unittest 625 { 626 struct HStorage 627 { 628 import containers.dynamicarray: DynamicArray; 629 DynamicArray!int storage; 630 } 631 auto hs = HStorage(); 632 } 633 634 version(emsi_containers_unittest) @nogc unittest 635 { 636 DynamicArray!char a; 637 const DynamicArray!char b = a ~ "def"; 638 a ~= "abc"; 639 a ~= b; 640 assert(a[] == "abcdef"); 641 a ~= a; 642 assert(a[] == "abcdefabcdef"); 643 } 644 645 version(emsi_containers_unittest) unittest 646 { 647 enum initialValue = 0x69FF5705DAD1AB6CUL; 648 enum payloadValue = 0x495343303356D18CUL; 649 650 static struct S 651 { 652 ulong value = initialValue; 653 @nogc: 654 @disable this(); 655 this(ulong value) { this.value = value; } 656 ~this() { assert(value == initialValue || value == payloadValue); } 657 } 658 659 auto s = S(payloadValue); 660 661 DynamicArray!S arr; 662 arr.insertBack(s); 663 arr ~= [s]; 664 } 665 666 version(emsi_containers_unittest) @nogc unittest 667 { 668 DynamicArray!int a; 669 a.resize(5, 42); 670 assert(a.length == 5); 671 assert(a[2] == 42); 672 a.resize(3, 17); 673 assert(a.length == 3); 674 assert(a[2] == 42); 675 676 struct Counter 677 { 678 @nogc: 679 static int count; 680 @disable this(); 681 this(int) { count++; } 682 this(this) { count++; } 683 ~this() { count--; } 684 } 685 686 DynamicArray!Counter b; 687 assert(Counter.count == 0); 688 static assert(!is(typeof(b.resize(5)))); 689 b.resize(5, Counter(0)); 690 assert(Counter.count == 5); 691 b.resize(3, Counter(0)); 692 assert(Counter.count == 3); 693 } 694 695 version(emsi_containers_unittest) @nogc unittest 696 { 697 struct S { int i = 42; @disable this(this); } 698 DynamicArray!S a; 699 a.resize(1); 700 assert(a[0].i == 42); 701 } 702 703 version(emsi_containers_unittest) unittest 704 { 705 import std.experimental.allocator.building_blocks.region : Region; 706 auto region = Region!Mallocator(1024); 707 708 auto arr = DynamicArray!(int, Region!(Mallocator)*, true)(®ion); 709 // reserve and insert back call the common form of reallocate 710 arr.reserve(10); 711 arr.insertBack(1); 712 assert(arr[0] == 1); 713 } 714 715 version(emsi_containers_unittest) unittest 716 { 717 auto arr = DynamicArray!int(); 718 arr.resize(5); 719 arr[] = [1, 2, 3, 4, 5]; 720 arr[1 .. 4] = [12, 13, 14]; 721 assert(arr[] == [1, 12, 13, 14, 5]); 722 } 723 724 version(emsi_containers_unittest) unittest 725 { 726 import std.experimental.allocator : RCIAllocator; 727 auto a = DynamicArray!(int, RCIAllocator)(RCIAllocator.init); 728 }